summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Weipert <git@mail.dweipert.de>2026-04-16 15:39:21 +0200
committerDaniel Weipert <git@mail.dweipert.de>2026-04-16 15:39:21 +0200
commit21593231a0e7de30004a8b4530047b4ad4680db7 (patch)
tree7a86fd3e99bd37631c48474bcdfb267d0763b4af /src
parentf3202403339e94f0186b9ff19e83c7a32fdad198 (diff)
implement against elementHEADmain
Diffstat (limited to 'src')
-rw-r--r--src/Controllers/Client/ClientController.php332
-rw-r--r--src/Controllers/Client/KeyController.php34
-rwxr-xr-xsrc/Controllers/Client/UserController.php61
-rwxr-xr-xsrc/Errors/NotFoundError.php19
-rwxr-xr-xsrc/Errors/UnauthorizedError.php4
5 files changed, 430 insertions, 20 deletions
diff --git a/src/Controllers/Client/ClientController.php b/src/Controllers/Client/ClientController.php
index cdce91e..28aea66 100644
--- a/src/Controllers/Client/ClientController.php
+++ b/src/Controllers/Client/ClientController.php
@@ -18,6 +18,7 @@ use App\Support\Parser;
use App\Support\RequestValidator;
use Matrix\Data\AccountData;
use Matrix\Data\Capabilities;
+use Matrix\Data\Capability\RoomVersionsCapability;
use Matrix\Data\DeviceLists;
use Matrix\Data\LoginFlow;
use Matrix\Data\Presence;
@@ -32,6 +33,7 @@ use Matrix\Data\Room\Timeline;
use Matrix\Data\Room\UnreadNotificationCounts;
use Matrix\Data\Ruleset;
use Matrix\Data\ToDevice;
+use Matrix\Data\UserId;
use Matrix\Enums\AuthenticationType;
use Matrix\Enums\ErrorCode;
use Matrix\Enums\LoginType;
@@ -128,7 +130,7 @@ class ClientController
return new JsonResponse(new ClientLoginPostResponse(
accessToken: $tokens->getAccessToken(),
- deviceId:$device->getId(),
+ deviceId: $device->getId(),
userId: $user->getId(),
expiresInMilliseconds: $tokens->getExpiresIn(),
refreshToken: $tokens->getRefreshToken(),
@@ -356,10 +358,130 @@ class ClientController
{
$user = User::authenticateWithRequest($request);
+ $notificationActionBase = [
+ "notify",
+ ];
+ $notificationActionSound = $notificationActionBase + [
+ [
+ "set_tweak" => "sound",
+ "value" => "default",
+ ],
+ ];
+ $notificationActionHighlight = $notificationActionBase + [
+ [
+ "set_tweak" => "highlight",
+ ],
+ ];
+ $notifactionActionSoundHighlight = $notificationActionSound + [
+ [
+ "set_tweak" => "highlight",
+ ],
+ ];
+
+ // @see https://spec.matrix.org/v1.16/client-server-api/#predefined-rules
return new JsonResponse([
"global" => new Ruleset(
content: [
new PushRule(
+ ruleId: ".m.rule.contains_user_name",
+ default: true,
+ enabled: false,
+ pattern: Parser::parseUser($user->getId())["username"],
+ actions: [
+ ...$notifactionActionSoundHighlight,
+ ],
+ ),
+ ],
+
+ override: [
+ new PushRule(
+ ruleId: ".m.rule.master",
+ default: true,
+ enabled: false,
+ conditions: [],
+ actions: [],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.suppress_notices",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "content.msgtype",
+ pattern: "m.notice",
+ ),
+ ],
+ actions: [],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.invite_for_me",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.member",
+ ),
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "content.membership",
+ pattern: "invite",
+ ),
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "state_key",
+ pattern: $user->getId(),
+ ),
+ ],
+ actions: [
+ ...$notificationActionSound,
+ ],
+ ),
+
+
+ new PushRule(
+ ruleId: ".m.rule.member_event",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.member",
+ ),
+ ],
+ actions: [],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.is_user_mention",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_PROPERTY_CONTAINS,
+ key: "content.m\\.mentions.user_ids",
+ value: $user->getId(),
+ ),
+ ],
+ actions: [
+ ...$notifactionActionSoundHighlight,
+ ],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.contains_display_name",
+ default: true,
+ enabled: false,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::CONTAINS_DISPLAY_NAME,
+ ),
+ ],
actions: [
"notify",
[
@@ -370,20 +492,117 @@ class ClientController
"set_tweak" => "highlight",
],
],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.is_room_mention",
default: true,
enabled: true,
- pattern: "alice",
- ruleId: ".m.rule.contains_user_name",
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_PROPERTY_IS,
+ key: "content.m\\.mentions.room",
+ value: true,
+ ),
+ new PushCondition(
+ kind: PushConditionKind::SENDER_NOTIFICATION_PERMISSION,
+ key: "room",
+ ),
+ ],
+ actions: [
+ ...$notificationActionHighlight,
+ ],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.roomnotif",
+ default: true,
+ enabled: false,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "content.body",
+ pattern: "@room",
+ ),
+ new PushCondition(
+ kind: PushConditionKind::SENDER_NOTIFICATION_PERMISSION,
+ key: "room",
+ ),
+ ],
+ actions: [
+ "notify",
+ [
+ "set_tweak" => "highlight",
+ ],
+ ],
),
- ],
- override: [
new PushRule(
+ ruleId: ".m.rule.tombstone",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.tombstone",
+ ),
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "state_key",
+ pattern: "",
+ ),
+ ],
+ actions: [
+ ...$notificationActionHighlight,
+ ],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.reaction",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.reaction",
+ ),
+ ],
actions: [],
- conditions: [],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.room.server_acl",
default: true,
- enabled: false,
- ruleId: ".m.rule.master",
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.server_acl",
+ ),
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "state_key",
+ pattern: "",
+ ),
+ ],
+ actions: [],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.suppress_edits",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_PROPERTY_IS,
+ key: "content.m\\.relates_to.rel_type",
+ value: "m.replace",
+ ),
+ ],
+ actions: [],
),
],
@@ -392,27 +611,95 @@ class ClientController
underride: [
new PushRule(
+ ruleId: ".m.rule.call",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.call.invite",
+ )
+ ],
actions: [
"notify",
[
"set_tweak" => "sound",
"value" => "ring",
],
- [
- "set_tweak" => "highlight",
- "value" => false,
- ],
],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.encrypted_room_one_to_one",
+ default: true,
+ enabled: true,
conditions: [
new PushCondition(
+ kind: PushConditionKind::ROOM_MEMBER_COUNT,
+ is: "2",
+ ),
+ new PushCondition(
kind: PushConditionKind::EVENT_MATCH,
key: "type",
- pattern: "m.call.invite",
- )
+ pattern: "m.room.encrypted",
+ ),
+ ],
+ actions: [
+ ...$notificationActionSound,
],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.room_one_to_one",
default: true,
enabled: true,
- ruleId: ".m.rule.master",
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::ROOM_MEMBER_COUNT,
+ is: "2",
+ ),
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.message",
+ ),
+ ],
+ actions: [
+ ...$notificationActionSound,
+ ],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.message",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.message",
+ ),
+ ],
+ actions: [
+ "notify",
+ ],
+ ),
+
+ new PushRule(
+ ruleId: ".m.rule.encrypted",
+ default: true,
+ enabled: true,
+ conditions: [
+ new PushCondition(
+ kind: PushConditionKind::EVENT_MATCH,
+ key: "type",
+ pattern: "m.room.encrypted",
+ ),
+ ],
+ actions: [
+ "notify",
+ ],
),
],
),
@@ -424,7 +711,12 @@ class ClientController
{
$user = User::authenticateWithRequest($request);
- return new JsonResponse(new Capabilities());
+ return new JsonResponse(new Capabilities(
+ roomVersions: new RoomVersionsCapability(
+ available: ["1" => "stable"],
+ default: "1",
+ ),
+ ));
}
#[Route(path: "/_matrix/client/v3/voip/turnServer", methods: ["GET"])]
@@ -439,4 +731,12 @@ class ClientController
"username" => "",
]);
}
+
+ #[Route(path: "/_matrix/client/v3/thirdparty/protocols", methods: ["GET"])]
+ public function thirdPartyProtocols(Request $request): Response
+ {
+ $user = User::authenticateWithRequest($request);
+
+ return new JsonResponse(new \stdClass());
+ }
}
diff --git a/src/Controllers/Client/KeyController.php b/src/Controllers/Client/KeyController.php
index 5e3245b..47f8933 100644
--- a/src/Controllers/Client/KeyController.php
+++ b/src/Controllers/Client/KeyController.php
@@ -2,7 +2,9 @@
namespace App\Controllers\Client;
+use App\App;
use App\Database;
+use App\Models\Device;
use App\Models\User;
use App\Support\RequestValidator;
use Matrix\Responses\ClientKeysUploadPostResponse;
@@ -93,10 +95,38 @@ class KeyController
$deviceKeys = $body["device_keys"];
$timeout = $body["timeout"] ?? 10000;
- foreach ($deviceKeys as $keysUserId => $deviceIds) {}
+ $downloadedDeviceKeys = [];
+ foreach ($deviceKeys as $keysUserId => $deviceIds) {
+ foreach ($deviceIds as $deviceId) {
+ $result = Database::getInstance()
+ ->query("select * from device_keys where user_id=:user_id and device_id=:device_id", [
+ "user_id" => $keysUserId,
+ "device_id" => $deviceId,
+ ])
+ ->fetch();
+ $device = Device::fetch($deviceId, $keysUserId);
+
+ $downloadedDeviceKeys[$keysUserId][$deviceId] = [
+ "algorithms" => $result["supported_algorithms"],
+ "keys" => $result["keys"],
+ "signatures" => $result["signatures"],
+ "device_id" => $result["device_id"],
+ "user_id" => $result["user_id"],
+ "unsigned" => [
+ "device_display_name" => $device->getName(),
+ ],
+ ];
+ }
+ }
+
+ // apply timeout
+ if ($timeout > 0) {
+ sleep(intval(($timeout / 1000) - App::getExectionTime()));
+ }
+
return new JsonResponse([
- "device_keys" => [],
+ "device_keys" => empty($downloadedDeviceKeys) ? new \stdClass() : $downloadedDeviceKeys,
]);
}
diff --git a/src/Controllers/Client/UserController.php b/src/Controllers/Client/UserController.php
index 277ba78..63442ec 100755
--- a/src/Controllers/Client/UserController.php
+++ b/src/Controllers/Client/UserController.php
@@ -4,10 +4,12 @@ namespace App\Controllers\Client;
use App\Database;
use App\Errors\AppException;
+use App\Errors\NotFoundError;
use App\Errors\UnauthorizedError;
use App\Models\Device;
use App\Models\User;
use App\Support\RequestValidator;
+use Cassandra\Exception\UnauthorizedException;
use Matrix\Enums\ErrorCode;
use Matrix\Responses\ClientAccountWhoamiGetResponse;
use Symfony\Component\HttpFoundation\Request;
@@ -120,4 +122,63 @@ class UserController
return new JsonResponse();
}
+
+ #[Route(path: "/_matrix/client/v3/user/{userId}/account_data/{type}", methods: ["GET"])]
+ public function getAccountData(Request $request): Response
+ {
+ $user = User::authenticateWithRequest($request);
+
+ $userId = $request->attributes->get("userId");
+ $type = $request->attributes->get("type");
+
+ if ($user->getId() !== $userId) {
+ throw new UnauthorizedError("Cannot get account data for other users.");
+ }
+
+ $value = Database::getInstance()
+ ->query("select value from account_data where user_id=:user_id and key=:key", [
+ "user_id" => $userId,
+ "key" => $type,
+ ])
+ ->fetchColumn();
+
+ if (empty($value)) {
+ throw new NotFoundError("Account data not found.");
+ }
+
+ return new JsonResponse([
+ $type => $value,
+ ]);
+ }
+
+ #[Route(path: "/_matrix/client/v3/user/{userId}/account_data/{type}", methods: ["PUT"])]
+ public function setAccountData(Request $request): Response
+ {
+ $user = User::authenticateWithRequest($request);
+ $body = json_decode($request->getContent(), true);
+ RequestValidator::validateJson();
+
+ $userId = $request->attributes->get("userId");
+ $type = $request->attributes->get("type");
+
+ if ($user->getId() !== $userId) {
+ throw new UnauthorizedError("Cannot add account data for other users.");
+ }
+
+ Database::getInstance()
+ ->query(
+ <<<SQL
+ insert into account_data (key, value, user_id) values (:key, :value, :user_id)
+ on conflict (user_id, key) do update set
+ value = excluded.value
+ SQL,
+ [
+ "key" => $type,
+ "value" => $request->getContent(),
+ "user_id" => $userId,
+ ]
+ );
+
+ return new JsonResponse();
+ }
}
diff --git a/src/Errors/NotFoundError.php b/src/Errors/NotFoundError.php
new file mode 100755
index 0000000..a128509
--- /dev/null
+++ b/src/Errors/NotFoundError.php
@@ -0,0 +1,19 @@
+<?php
+
+namespace App\Errors;
+
+use Matrix\Enums\ErrorCode;
+use Symfony\Component\HttpFoundation\Response;
+
+class NotFoundError extends Exception
+{
+ public function __construct(string $message = "404")
+ {
+ parent::__construct(ErrorCode::NOT_FOUND, $message, Response::HTTP_NOT_FOUND);
+ }
+
+ public function getAdditionalData(): array
+ {
+ return [];
+ }
+}
diff --git a/src/Errors/UnauthorizedError.php b/src/Errors/UnauthorizedError.php
index cd9981f..e164597 100755
--- a/src/Errors/UnauthorizedError.php
+++ b/src/Errors/UnauthorizedError.php
@@ -7,9 +7,9 @@ use Symfony\Component\HttpFoundation\Response;
class UnauthorizedError extends Exception
{
- public function __construct()
+ public function __construct(string $message = "Unauthorized")
{
- parent::__construct(ErrorCode::FORBIDDEN, "Unauthorized", Response::HTTP_UNAUTHORIZED);
+ parent::__construct(ErrorCode::FORBIDDEN, $message, Response::HTTP_UNAUTHORIZED);
}
public function getAdditionalData(): array