summaryrefslogtreecommitdiff
path: root/matrix-specification/Data
diff options
context:
space:
mode:
Diffstat (limited to 'matrix-specification/Data')
-rw-r--r--matrix-specification/Data/AccountData.php23
-rw-r--r--matrix-specification/Data/AuthenticationData.php24
-rw-r--r--matrix-specification/Data/Contact.php28
-rw-r--r--matrix-specification/Data/DeviceKeys.php31
-rw-r--r--matrix-specification/Data/DeviceLists.php24
-rw-r--r--matrix-specification/Data/DiscoveryInformation.php30
-rw-r--r--matrix-specification/Data/Filters/EventFilter.php36
-rw-r--r--matrix-specification/Data/Filters/RoomEventFilter.php50
-rw-r--r--matrix-specification/Data/Filters/RoomFilter.php39
-rw-r--r--matrix-specification/Data/HomeServerInformation.php18
-rw-r--r--matrix-specification/Data/IdentityServerInformation.php18
-rw-r--r--matrix-specification/Data/KeyObject.php32
-rw-r--r--matrix-specification/Data/LoginFlow.php31
-rw-r--r--matrix-specification/Data/Presence.php23
-rw-r--r--matrix-specification/Data/Room/Ephemeral.php21
-rw-r--r--matrix-specification/Data/Room/InviteState.php23
-rw-r--r--matrix-specification/Data/Room/InvitedRoom.php18
-rw-r--r--matrix-specification/Data/Room/JoinedRoom.php37
-rw-r--r--matrix-specification/Data/Room/KnockState.php23
-rw-r--r--matrix-specification/Data/Room/KnockedRoom.php18
-rw-r--r--matrix-specification/Data/Room/LeftRoom.php30
-rw-r--r--matrix-specification/Data/Room/RoomSummary.php25
-rw-r--r--matrix-specification/Data/Room/Rooms.php62
-rw-r--r--matrix-specification/Data/Room/State.php23
-rw-r--r--matrix-specification/Data/Room/ThreadNotificationCounts.php20
-rw-r--r--matrix-specification/Data/Room/Timeline.php27
-rw-r--r--matrix-specification/Data/Room/UnreadNotificationCounts.php20
-rw-r--r--matrix-specification/Data/ToDevice.php23
-rw-r--r--matrix-specification/Data/UserId.php17
-rw-r--r--matrix-specification/Data/UserIdentifier.php45
30 files changed, 839 insertions, 0 deletions
diff --git a/matrix-specification/Data/AccountData.php b/matrix-specification/Data/AccountData.php
new file mode 100644
index 0000000..fa3e4a3
--- /dev/null
+++ b/matrix-specification/Data/AccountData.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Events\Event;
+
+class AccountData implements \JsonSerializable
+{
+ /**
+ * @param Event[] $events
+ */
+ public function __construct(
+ private array $events
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/AuthenticationData.php b/matrix-specification/Data/AuthenticationData.php
new file mode 100644
index 0000000..64fdd95
--- /dev/null
+++ b/matrix-specification/Data/AuthenticationData.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Matrix\Data;
+
+class AuthenticationData implements \JsonSerializable
+{
+ public function __construct(
+ private ?string $session = null,
+ private ?string $type = null,
+ )
+ {
+ # TODO: throw for session and type
+ # TODO: throw for keys dependent on login type
+ # throw new \InvalidArgumentException("at least one is required");
+ }
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "session" => $this->session,
+ "type" => $this->type,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Contact.php b/matrix-specification/Data/Contact.php
new file mode 100644
index 0000000..54a48e4
--- /dev/null
+++ b/matrix-specification/Data/Contact.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Enums\Role;
+
+class Contact implements \JsonSerializable
+{
+ public function __construct(
+ private Role|string $role,
+ private ?string $emailAddress = null,
+ private ?string $matrixId = null,
+ )
+ {
+ if (is_null($emailAddress) && is_null($matrixId)) {
+ throw new \InvalidArgumentException("at least one of emailAddress or matrixId is required");
+ }
+ }
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "email_address" => $this->emailAddress,
+ "matrix_id" => $this->matrixId,
+ "role" => $this->role,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/DeviceKeys.php b/matrix-specification/Data/DeviceKeys.php
new file mode 100644
index 0000000..034263e
--- /dev/null
+++ b/matrix-specification/Data/DeviceKeys.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Matrix\Data;
+
+class DeviceKeys implements \JsonSerializable
+{
+ /**
+ * @param string[] $algorithms
+ * @param array<string, string> $keys
+ * @param array<string, array<string, string>> $signatures
+ */
+ public function __construct(
+ private array $algorithms,
+ private string $deviceId,
+ private array $keys,
+ private array $signatures,
+ private string $userId,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "algorithms" => $this->algorithms,
+ "device_id" => $this->deviceId,
+ "keys" => $this->keys,
+ "signatures" => $this->signatures,
+ "user_id" => $this->userId,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/DeviceLists.php b/matrix-specification/Data/DeviceLists.php
new file mode 100644
index 0000000..33d9459
--- /dev/null
+++ b/matrix-specification/Data/DeviceLists.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Matrix\Data;
+
+class DeviceLists implements \JsonSerializable
+{
+ /**
+ * @param string[] $changed
+ * @param string[] $left
+ */
+ public function __construct(
+ private array $changed = [],
+ private array $left = [],
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "changed" => $this->events,
+ "left" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/DiscoveryInformation.php b/matrix-specification/Data/DiscoveryInformation.php
new file mode 100644
index 0000000..f4eda64
--- /dev/null
+++ b/matrix-specification/Data/DiscoveryInformation.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Matrix\Data;
+
+class DiscoveryInformation implements \JsonSerializable
+{
+ /**
+ * @param array<string, array<mixed, mixed>> $otherProperties
+ */
+ public function __construct(
+ private HomeServerInformation $homeServerInformation,
+ private IdentityServerInformation $identityServerInformation,
+ private ?array $otherProperties = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter(
+ array_merge(
+ [
+ "m.homeserver" => $this->homeServerInformation,
+ "m.identity_server" => $this->identityServerInformation,
+ ],
+ $this->otherProperties ?? [],
+ ),
+ fn ($value) => ! is_null($value)
+ );
+ }
+}
diff --git a/matrix-specification/Data/Filters/EventFilter.php b/matrix-specification/Data/Filters/EventFilter.php
new file mode 100644
index 0000000..cd8fdf2
--- /dev/null
+++ b/matrix-specification/Data/Filters/EventFilter.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Matrix\Data\Filters;
+
+class EventFilter implements \JsonSerializable
+{
+ /**
+ * @param string[] $notSenders
+ * @param string[] $notTypes
+ * @param string[] $senders
+ * @param string[] $types
+ */
+ public function __construct(
+ private ?int $limit = null,
+ private ?array $notSenders = null,
+ private ?array $notTypes = null,
+ private ?array $senders = null,
+ private ?array $types = null,
+ )
+ {
+ if (! is_null($limit) && $limit <= 0) {
+ throw new \InvalidArgumentException("limit must be an integer greater than 0");
+ }
+ }
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "limit" => $this->limit,
+ "not_senders" => $this->notSenders,
+ "not_types" => $this->notTypes,
+ "senders" => $this->senders,
+ "types" => $this->types,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Filters/RoomEventFilter.php b/matrix-specification/Data/Filters/RoomEventFilter.php
new file mode 100644
index 0000000..b7ce87b
--- /dev/null
+++ b/matrix-specification/Data/Filters/RoomEventFilter.php
@@ -0,0 +1,50 @@
+<?php
+
+namespace Matrix\Data\Filters;
+
+class RoomEventFilter extends EventFilter
+{
+ /**
+ * @param string[] $notRooms
+ * @param string[] $notSenders
+ * @param string[] $notTypes
+ * @param string[] $rooms
+ * @param string[] $senders
+ * @param string[] $types
+ */
+ public function __construct(
+ private ?bool $containsUrl = null,
+ private ?bool $includeRedundantMembers = null,
+ private ?bool $lazyLoadMembers = null,
+ ?int $limit = null,
+ private ?array $notRooms = null,
+ ?array $notSenders = null,
+ ?array $notTypes = null,
+ private ?array $rooms = null,
+ ?array $senders = null,
+ ?array $types = null,
+ private ?bool $unreadThreadNotifications = null,
+ )
+ {
+ parent::__construct($limit, $notSenders, $notTypes, $senders, $types);
+ }
+
+ public function setDefaults(): void
+ {
+ $this->includeRedundantMembers ??= false;
+ $this->lazyLoadMembers ??= false;
+ $this->unreadThreadNotifications ??= false;
+ }
+
+ public function jsonSerialize(): array
+ {
+ return parent::jsonSerialize() + array_filter([
+ "contains_url" => $this->containsUrl,
+ "include_redundant_members" => $this->includeRedundantMembers,
+ "lazy_load_members" => $this->lazyLoadMembers,
+ "not_rooms" => $this->notRooms,
+ "rooms" => $this->rooms,
+ "unread_thread_notifications" => $this->unreadThreadNotifications,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Filters/RoomFilter.php b/matrix-specification/Data/Filters/RoomFilter.php
new file mode 100644
index 0000000..fe741c5
--- /dev/null
+++ b/matrix-specification/Data/Filters/RoomFilter.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace Matrix\Data\Filters;
+
+class RoomFilter
+{
+ /**
+ * @param string[] $notRooms
+ * @param string[] $rooms
+ */
+ public function __construct(
+ private ?RoomEventFilter $accountData = null,
+ private ?RoomEventFilter $ephemeral = null,
+ private ?bool $includeLeave = null,
+ private ?array $notRooms = null,
+ private ?array $rooms = null,
+ private ?RoomEventFilter $state = null,
+ private ?RoomEventFilter $timeline = null,
+ )
+ {}
+
+ public function setDefaults(): void
+ {
+ $this->includeLeave ??= false;
+ }
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "account_data" => $this->accountData,
+ "ephemeral" => $this->ephemeral,
+ "include_leave" => $this->includeLeave,
+ "not_rooms" => $this->notRooms,
+ "rooms" => $this->rooms,
+ "state" => $this->state,
+ "timeline" => $this->timeline,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/HomeServerInformation.php b/matrix-specification/Data/HomeServerInformation.php
new file mode 100644
index 0000000..15c9f10
--- /dev/null
+++ b/matrix-specification/Data/HomeServerInformation.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Matrix\Data;
+
+class HomeServerInformation implements \JsonSerializable
+{
+ public function __construct(
+ private string $baseUrl,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "base_url" => $this->baseUrl,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/IdentityServerInformation.php b/matrix-specification/Data/IdentityServerInformation.php
new file mode 100644
index 0000000..bed8323
--- /dev/null
+++ b/matrix-specification/Data/IdentityServerInformation.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Matrix\Data;
+
+class IdentityServerInformation implements \JsonSerializable
+{
+ public function __construct(
+ private string $baseUrl,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "base_url" => $this->baseUrl,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/KeyObject.php b/matrix-specification/Data/KeyObject.php
new file mode 100644
index 0000000..120350c
--- /dev/null
+++ b/matrix-specification/Data/KeyObject.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Matrix\Data;
+
+class KeyObject implements \JsonSerializable
+{
+ /**
+ * @param array<string, array> $signatures
+ */
+ public function __construct(
+ private string $key,
+ private array $signatures,
+ private bool $isFallback = false,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ $keyObject = [
+ "key" => $this->key,
+ "signatures" => $this->signatures,
+ ];
+
+ if ($this->isFallback) {
+ $keyObject += [
+ "fallback" => true,
+ ];
+ }
+
+ return $keyObject;
+ }
+}
diff --git a/matrix-specification/Data/LoginFlow.php b/matrix-specification/Data/LoginFlow.php
new file mode 100644
index 0000000..6874aad
--- /dev/null
+++ b/matrix-specification/Data/LoginFlow.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Enums\LoginType;
+
+class LoginFlow implements \JsonSerializable
+{
+ public function __construct(
+ private LoginType $type,
+ private ?bool $getLoginToken = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ $loginFlow = [
+ "type" => $this->type,
+ ];
+
+ $loginFlow += match ($this->type) {
+ LoginType::TOKEN => [
+ "get_login_token" => $this->getLoginToken,
+ ],
+
+ default => [],
+ };
+
+ return $loginFlow;
+ }
+}
diff --git a/matrix-specification/Data/Presence.php b/matrix-specification/Data/Presence.php
new file mode 100644
index 0000000..971343f
--- /dev/null
+++ b/matrix-specification/Data/Presence.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Events\PresenceEvent;
+
+class Presence implements \JsonSerializable
+{
+ /**
+ * @param PresenceEvent[] $events
+ */
+ public function __construct(
+ private array $events
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/Ephemeral.php b/matrix-specification/Data/Room/Ephemeral.php
new file mode 100644
index 0000000..bcaf22f
--- /dev/null
+++ b/matrix-specification/Data/Room/Ephemeral.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class Ephemeral implements \JsonSerializable
+{
+ /**
+ * @param Event[] $events
+ */
+ public function __construct(
+ private array $events,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/InviteState.php b/matrix-specification/Data/Room/InviteState.php
new file mode 100644
index 0000000..ca468ee
--- /dev/null
+++ b/matrix-specification/Data/Room/InviteState.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+use Matrix\Events\StrippedStateEvent;
+
+class InviteState implements \JsonSerializable
+{
+ /**
+ * @param StrippedStateEvent[] $events
+ */
+ public function __construct(
+ private array $events,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/InvitedRoom.php b/matrix-specification/Data/Room/InvitedRoom.php
new file mode 100644
index 0000000..f081861
--- /dev/null
+++ b/matrix-specification/Data/Room/InvitedRoom.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class InvitedRoom implements \JsonSerializable
+{
+ public function __construct(
+ private InviteState $inviteState,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "invite_state" => $this->inviteState,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/JoinedRoom.php b/matrix-specification/Data/Room/JoinedRoom.php
new file mode 100644
index 0000000..0057071
--- /dev/null
+++ b/matrix-specification/Data/Room/JoinedRoom.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+use Matrix\Data\AccountData;
+
+class JoinedRoom implements \JsonSerializable
+{
+ /**
+ * @param array<string, ThreadNotificationCounts>
+ */
+ public function __construct(
+ private ?AccountData $accountData = null,
+ private ?Ephemeral $ephemeral = null,
+ private ?State $state = null,
+ private ?State $stateAfter = null,
+ private ?RoomSummary $summary = null,
+ private ?Timeline $timeline = null,
+ private ?UnreadNotificationCounts $unreadNotifications = null,
+ private ?array $unreadThreadNotifications = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "account_data" => $this->inviteState,
+ "ephemeral" => $this->ephemeral,
+ "state" => $this->state,
+ "state_after" => $this->stateAfter,
+ "summary" => $this->summary,
+ "timeline" => $this->timeline,
+ "unread_notifications" => $this->unreadNotifications,
+ "unreadThreadNotifications" => $this->unreadThreadNotifications,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Room/KnockState.php b/matrix-specification/Data/Room/KnockState.php
new file mode 100644
index 0000000..e294da8
--- /dev/null
+++ b/matrix-specification/Data/Room/KnockState.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+use Matrix\Events\StrippedStateEvent;
+
+class KnockState implements \JsonSerializable
+{
+ /**
+ * @param StrippedStateEvent[] $events
+ */
+ public function __construct(
+ private array $events,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/KnockedRoom.php b/matrix-specification/Data/Room/KnockedRoom.php
new file mode 100644
index 0000000..cbea19f
--- /dev/null
+++ b/matrix-specification/Data/Room/KnockedRoom.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class KnockedRoom implements \JsonSerializable
+{
+ public function __construct(
+ private KnockState $knockState,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "knock_state" => $this->knockState,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/LeftRoom.php b/matrix-specification/Data/Room/LeftRoom.php
new file mode 100644
index 0000000..64b9462
--- /dev/null
+++ b/matrix-specification/Data/Room/LeftRoom.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+use Matrix\Data\AccountData;
+
+/**
+ * @see https://spec.matrix.org/v1.16/client-server-api/#get_matrixclientv3sync_response-200_left-room
+ * TODO: validate against request. add ValidatesAgainstRequest interface? and MatrixRequest base class?
+ */
+class LeftRoom implements \JsonSerializable
+{
+ public function __construct(
+ private ?AccountData $accountData = null,
+ private ?State $state = null,
+ private ?State $stateAfter = null,
+ private ?Timeline $timeline = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "account_data" => $this->inviteState,
+ "state" => $this->state,
+ "state_after" => $this->stateAfter,
+ "timeline" => $this->timeline,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Room/RoomSummary.php b/matrix-specification/Data/Room/RoomSummary.php
new file mode 100644
index 0000000..5ac86a8
--- /dev/null
+++ b/matrix-specification/Data/Room/RoomSummary.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class RoomSummary implements \JsonSerializable
+{
+ /**
+ * @param string[] $heroes
+ */
+ public function __construct(
+ private ?array $heroes = null,
+ private ?int $invitedMemberCount = null,
+ private ?int $joinedMemberCount = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "m.heroes" => $this->heroes,
+ "m.invited_member_count" => $this->invitedMemberCount,
+ "m.joined_member_count" => $this->joinedMemberCount,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Room/Rooms.php b/matrix-specification/Data/Room/Rooms.php
new file mode 100644
index 0000000..1a84608
--- /dev/null
+++ b/matrix-specification/Data/Room/Rooms.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class Rooms implements \JsonSerializable
+{
+ /**
+ * @param array<string, InvitedRoom> $invite
+ * @param array<string, JoinedRoom> $join
+ * @param array<string, KnockedRoom> $knock
+ * @param array<string, LeftRoom> $leave
+ */
+ public function __construct(
+ private ?array $invite = null,
+ private ?array $join = null,
+ private ?array $knock = null,
+ private ?array $leave = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "invite" => $this->invite ?? new \stdClass(),
+ "join" => $this->join ?? new \stdClass(),
+ "knock" => $this->knock ?? new \stdClass(),
+ "leave" => $this->leave ?? new \stdClass(),
+ ];
+ }
+
+ /**
+ * @return InvitedRoom[]
+ */
+ public function getInvite(): ?array
+ {
+ return $this->invite;
+ }
+
+ /**
+ * @return JoinedRoom[]
+ */
+ public function getJoined(): ?array
+ {
+ return $this->join;
+ }
+
+ /**
+ * @return KnockedRoom[]
+ */
+ public function getKnocked(): ?array
+ {
+ return $this->knock;
+ }
+
+ /**
+ * @return LeftRoom[]
+ */
+ public function getLeft(): ?array
+ {
+ return $this->leave;
+ }
+}
diff --git a/matrix-specification/Data/Room/State.php b/matrix-specification/Data/Room/State.php
new file mode 100644
index 0000000..be9f0ef
--- /dev/null
+++ b/matrix-specification/Data/Room/State.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+use Matrix\Events\ClientEventWithoutRoomId;
+
+class State implements \JsonSerializable
+{
+ /**
+ * @param ClientEventWithoutRoomId[] $events
+ */
+ public function __construct(
+ private array $events,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/Room/ThreadNotificationCounts.php b/matrix-specification/Data/Room/ThreadNotificationCounts.php
new file mode 100644
index 0000000..602de61
--- /dev/null
+++ b/matrix-specification/Data/Room/ThreadNotificationCounts.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class ThreadNotificationCounts implements \JsonSerializable
+{
+ public function __construct(
+ private ?int $highlightCount = null,
+ private ?int $notificationCount = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "highlight_count" => $this->highlightCount,
+ "notification_count" => $this->notificationCount,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Room/Timeline.php b/matrix-specification/Data/Room/Timeline.php
new file mode 100644
index 0000000..8d70ddd
--- /dev/null
+++ b/matrix-specification/Data/Room/Timeline.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+use Matrix\Events\ClientEventWithoutRoomId;
+
+class Timeline implements \JsonSerializable
+{
+ /**
+ * @param ClientEventWithoutRoomId[] $events
+ */
+ public function __construct(
+ private array $events,
+ private ?bool $limited = null,
+ private ?string $previousBatch = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "events" => $this->events,
+ "limited" => $this->limited,
+ "prev_batch" => $this->previousBatch,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/Room/UnreadNotificationCounts.php b/matrix-specification/Data/Room/UnreadNotificationCounts.php
new file mode 100644
index 0000000..f00a6a5
--- /dev/null
+++ b/matrix-specification/Data/Room/UnreadNotificationCounts.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Matrix\Data\Room;
+
+class UnreadNotificationCounts implements \JsonSerializable
+{
+ public function __construct(
+ private ?int $highlightCount = null,
+ private ?int $notificationCount = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return array_filter([
+ "highlight_count" => $this->highlightCount,
+ "notification_count" => $this->notificationCount,
+ ], fn ($value) => ! is_null($value));
+ }
+}
diff --git a/matrix-specification/Data/ToDevice.php b/matrix-specification/Data/ToDevice.php
new file mode 100644
index 0000000..362ce06
--- /dev/null
+++ b/matrix-specification/Data/ToDevice.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Events\SenderEvent;
+
+class ToDevice implements \JsonSerializable
+{
+ /**
+ * @param SenderEvent[] $events
+ */
+ public function __construct(
+ private array $events
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ return [
+ "events" => $this->events,
+ ];
+ }
+}
diff --git a/matrix-specification/Data/UserId.php b/matrix-specification/Data/UserId.php
new file mode 100644
index 0000000..01a5e9e
--- /dev/null
+++ b/matrix-specification/Data/UserId.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Matrix\Data;
+
+class UserId
+{
+ public static function validate(): bool
+ {}
+
+ public static function parse(): array
+ {}
+
+ public static function build(string $username, string $serverName): string
+ {
+ return "@$username:$serverName";
+ }
+}
diff --git a/matrix-specification/Data/UserIdentifier.php b/matrix-specification/Data/UserIdentifier.php
new file mode 100644
index 0000000..8fcdae0
--- /dev/null
+++ b/matrix-specification/Data/UserIdentifier.php
@@ -0,0 +1,45 @@
+<?php
+
+namespace Matrix\Data;
+
+use Matrix\Enums\UserIdentifierType;
+
+class UserIdentifier implements \JsonSerializable
+{
+ public function __construct(
+ private UserIdentifierType $type,
+ private ?string $user = null,
+ private ?string $thirdPartyMedium = null,
+ private ?string $thirdPartyAddress = null,
+ private ?string $phoneCountry = null,
+ private ?string $phoneNumber = null,
+ )
+ {}
+
+ public function jsonSerialize(): array
+ {
+ $userIdentifier = [
+ "type" => $this->type,
+ ];
+
+ $userIdentifier += match ($this->type) {
+ UserIdentifierType::USER => [
+ "user" => $this->user,
+ ],
+
+ UserIdentifierType::THIRDPARTY => [
+ "medium" => $this->thirdPartyMedium,
+ "address" => $this->thirdPartyAddress,
+ ],
+
+ UserIdentifierType::PHONE => [
+ "country" => $this->phoneCountry,
+ "phone" => $this->phoneNumber,
+ ],
+
+ default => [],
+ };
+
+ return $userIdentifier;
+ }
+}