Skip to content

Commit 41d5c9b

Browse files
authored
Merge pull request #16 from Gnuxie/gnuxie/room-shutdown
Gnuxie/room shutdown
2 parents 0caae26 + d1c8d6e commit 41d5c9b

File tree

8 files changed

+248
-20
lines changed

8 files changed

+248
-20
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ All notable changes to this project will be documented in this file.
1111
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
1212
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1313

14+
## Unreleased
15+
16+
### Added
17+
18+
- Support for Synapse admin endpoint for querying the block status of
19+
a room
20+
21+
- Synapse admin endpoint for shutdown V2
22+
23+
- Synapse admin endpoint for room details.
24+
25+
### Changed
26+
27+
- Support for MPS's new SHA256HashReverser in the `RoomStateManagerFactory`.
28+
1429
## [2.10.1] - 2025-03-03
1530

1631
### Fixed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@commitlint/config-conventional": "^17.4.4",
2929
"@eslint/js": "^9.6.0",
3030
"@sinclair/typebox": "0.34.13",
31-
"@the-draupnir-project/matrix-basic-types": "^0.2.0",
31+
"@the-draupnir-project/matrix-basic-types": "1.3.0",
3232
"@types/crypto-js": "^4.1.2",
3333
"@types/eslint__js": "^8.42.3",
3434
"@types/glob-to-regexp": "^0.4.1",
@@ -40,7 +40,7 @@
4040
"jest": "^29.4.3",
4141
"lint-staged": "^13.1.2",
4242
"matrix-bot-sdk": ">=0.6.4",
43-
"matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@2.10.0",
43+
"matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@3.0.0",
4444
"postcss": "^8.4.21",
4545
"prettier": "^2.8.4",
4646
"ts-jest": "^29.0.5",
@@ -73,9 +73,9 @@
7373
},
7474
"peerDependencies": {
7575
"@sinclair/typebox": "0.34.13",
76-
"@the-draupnir-project/matrix-basic-types": "^0.2.0",
76+
"@the-draupnir-project/matrix-basic-types": "1.3.0",
7777
"matrix-bot-sdk": ">=0.6.4",
78-
"matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@2.10.0"
78+
"matrix-protection-suite": "npm:@gnuxie/matrix-protection-suite@3.0.0"
7979
},
8080
"publishConfig": {
8181
"@gnuxie:registry": "https://registry.npmjs.org"

src/ClientManagement/RoomStateManagerFactory.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import {
2626
RoomStateMembershipRevisionIssuer,
2727
RoomStatePolicyRoomRevisionIssuer,
2828
RoomStateRevisionIssuer,
29+
SHA256HashStore,
2930
StandardPolicyRoomRevision,
3031
StandardRoomMembershipRevision,
3132
StandardRoomStateRevisionIssuer,
33+
StandardSHA256HashReverser,
3234
StateEvent,
3335
isError,
3436
isOk,
@@ -117,13 +119,13 @@ export class RoomStateManagerFactory {
117119
if (isError(roomStateIssuer)) {
118120
return roomStateIssuer;
119121
}
120-
return Ok(
121-
new RoomStatePolicyRoomRevisionIssuer(
122-
room,
123-
StandardPolicyRoomRevision.blankRevision(room),
124-
roomStateIssuer.ok
125-
)
122+
const issuer = new RoomStatePolicyRoomRevisionIssuer(
123+
room,
124+
StandardPolicyRoomRevision.blankRevision(room),
125+
roomStateIssuer.ok
126126
);
127+
this.sha256Reverser?.addPolicyRoomRevisionIssuer(issuer);
128+
return Ok(issuer);
127129
});
128130

129131
private readonly roomMembershipIssuers: InternedInstanceFactory<
@@ -149,11 +151,15 @@ export class RoomStateManagerFactory {
149151
);
150152
});
151153

154+
private readonly sha256Reverser = this.hashStore
155+
? new StandardSHA256HashReverser(this.hashStore)
156+
: undefined;
152157
constructor(
153158
public readonly clientsInRoomMap: ClientsInRoomMap,
154159
private readonly clientProvider: ClientForUserID,
155160
private readonly eventDecoder: EventDecoder,
156-
private readonly roomStateBackingStore?: RoomStateBackingStore
161+
private readonly roomStateBackingStore: RoomStateBackingStore | undefined,
162+
private readonly hashStore: SHA256HashStore | undefined
157163
) {
158164
// nothing to do.
159165
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-FileCopyrightText: 2025 Gnuxie <[email protected]>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
import { Type } from '@sinclair/typebox';
6+
import { EDStatic, StringUserIDSchema } from 'matrix-protection-suite';
7+
8+
export type BlockStatusResponse = EDStatic<typeof BlockStatusResponse>;
9+
export const BlockStatusResponse = Type.Object({
10+
block: Type.Boolean({
11+
description: 'True if the room is blocked, otherwise false.',
12+
}),
13+
user_id: Type.Optional(
14+
Type.Union([StringUserIDSchema], {
15+
description:
16+
"User ID of the person who added the room to the blocking list. Only present if 'block' is true.",
17+
})
18+
),
19+
});
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// SPDX-FileCopyrightText: 2025 Gnuxie <[email protected]>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
import { Type } from '@sinclair/typebox';
6+
import {
7+
EDStatic,
8+
StringRoomAliasSchema,
9+
StringRoomIDSchema,
10+
StringUserIDSchema,
11+
} from 'matrix-protection-suite';
12+
13+
export type RoomDetailsResponse = EDStatic<typeof RoomDetailsResponse>;
14+
export const RoomDetailsResponse = Type.Object({
15+
room_id: Type.Union(
16+
[StringRoomIDSchema],
17+
Type.String({ description: 'The ID of the room.' })
18+
),
19+
name: Type.Optional(
20+
Type.Union([Type.String(), Type.Null()], {
21+
description: 'The name of the room.',
22+
})
23+
),
24+
topic: Type.Optional(
25+
Type.Union([Type.String(), Type.Null()], {
26+
description: 'The topic of the room.',
27+
})
28+
),
29+
avatar: Type.Optional(
30+
Type.Union([Type.String(), Type.Null()], {
31+
description: 'The mxc URI to the avatar of the room.',
32+
})
33+
),
34+
canonical_alias: Type.Optional(
35+
Type.Union([StringRoomAliasSchema, Type.String(), Type.Null()], {
36+
description: 'The canonical (main) alias address of the room.',
37+
})
38+
),
39+
joined_members: Type.Number({
40+
description: 'How many users are currently in the room.',
41+
}),
42+
joined_local_members: Type.Number({
43+
description: 'How many local users are currently in the room.',
44+
}),
45+
joined_local_devices: Type.Number({
46+
description: 'How many local devices are currently in the room.',
47+
}),
48+
version: Type.String({ description: 'The version of the room as a string.' }),
49+
creator: Type.Union([StringUserIDSchema], {
50+
description: 'The user_id of the room creator.',
51+
}),
52+
encryption: Type.Union([Type.String(), Type.Null()], {
53+
description:
54+
'Algorithm of end-to-end encryption of messages. Null if encryption is not active.',
55+
}),
56+
federatable: Type.Boolean({
57+
description: 'Whether users on other servers can join this room.',
58+
}),
59+
public: Type.Boolean({
60+
description: 'Whether the room is visible in the room directory.',
61+
}),
62+
join_rules: Type.Union(
63+
[
64+
Type.Literal('public'),
65+
Type.Literal('knock'),
66+
Type.Literal('invite'),
67+
Type.Literal('private'),
68+
],
69+
{
70+
description:
71+
'The type of rules used for users wishing to join this room.',
72+
}
73+
),
74+
guest_access: Type.Union(
75+
[Type.Literal('can_join'), Type.Literal('forbidden'), Type.Null()],
76+
{ description: 'Whether guests can join the room.' }
77+
),
78+
history_visibility: Type.Union(
79+
[
80+
Type.Literal('invited'),
81+
Type.Literal('joined'),
82+
Type.Literal('shared'),
83+
Type.Literal('world_readable'),
84+
],
85+
{ description: 'Who can see the room history.' }
86+
),
87+
state_events: Type.Number({
88+
description:
89+
'Total number of state events in the room. Represents the complexity of the room.',
90+
}),
91+
room_type: Type.Union([Type.String(), Type.Null()], {
92+
description:
93+
"The type of the room from the room's creation event, e.g., 'm.space'. Null if not defined.",
94+
}),
95+
forgotten: Type.Boolean({
96+
description: 'Whether all local users have forgotten the room.',
97+
}),
98+
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// SPDX-FileCopyrightText: 2025 Gnuxie <[email protected]>
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
import { Type } from '@sinclair/typebox';
6+
import { EDStatic } from 'matrix-protection-suite';
7+
8+
export type SynapseRoomShutdownV2RequestBody = EDStatic<
9+
typeof SynapseRoomShutdownV2RequestBody
10+
>;
11+
export const SynapseRoomShutdownV2RequestBody = Type.Object(
12+
{
13+
new_room_user_id: Type.Optional(
14+
Type.String({
15+
description:
16+
'User ID of the creator/admin for the new room. Must be local but not necessarily registered.',
17+
})
18+
),
19+
room_name: Type.Optional(
20+
Type.String({
21+
description:
22+
"Name of the new room. Defaults to 'Content Violation Notification'.",
23+
})
24+
),
25+
message: Type.Optional(
26+
Type.String({
27+
description:
28+
"First message in the new room. Defaults to 'Sharing illegal content on this server is not permitted and rooms in violation will be blocked.'",
29+
})
30+
),
31+
block: Type.Optional(
32+
Type.Boolean({
33+
description:
34+
'If true, prevents future attempts to join the room. Defaults to false.',
35+
})
36+
),
37+
purge: Type.Optional(
38+
Type.Boolean({
39+
description:
40+
'If true, removes all traces of the room from the database. Defaults to true.',
41+
})
42+
),
43+
force_purge: Type.Optional(
44+
Type.Boolean({
45+
description:
46+
"If true, forces purge even if local users are still in the room. Only applies if 'purge' is true.",
47+
})
48+
),
49+
},
50+
{ minProperties: 1, description: 'Request body must not be empty.' }
51+
);

src/SynapseAdmin/SynapseAdminClient.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ import {
4444
StringUserID,
4545
} from '@the-draupnir-project/matrix-basic-types';
4646
import { Type } from '@sinclair/typebox';
47-
import { resultifyBotSDKRequestError } from '../Client/BotSDKBaseClient';
47+
import {
48+
resultifyBotSDKRequestError,
49+
resultifyBotSDKRequestErrorWith404AsUndefined,
50+
} from '../Client/BotSDKBaseClient';
51+
import { SynapseRoomShutdownV2RequestBody } from './ShutdownV2Endpoint';
52+
import { BlockStatusResponse } from './BlockStatusEndpoint';
53+
import { RoomDetailsResponse } from './RoomDetailsEndpoint';
4854

4955
const ReportPollResponse = Type.Object({
5056
event_reports: Type.Array(SynapseReport),
@@ -177,4 +183,37 @@ export class SynapseAdminClient {
177183
}
178184
return Value.Decode(ReportPollResponse, response.ok);
179185
}
186+
187+
public async shutdownRoomV2(
188+
roomID: StringRoomID,
189+
options: SynapseRoomShutdownV2RequestBody
190+
): Promise<ActionResult<void>> {
191+
const endpoint = `/_synapse/admin/v2/rooms/${encodeURIComponent(roomID)}`;
192+
return await this.client
193+
.doRequest('DELETE', endpoint, null, options)
194+
.then(() => Ok(undefined), resultifyBotSDKRequestError);
195+
}
196+
197+
public async getBlockStatus(
198+
roomID: StringRoomID
199+
): Promise<ActionResult<BlockStatusResponse>> {
200+
const endpoint = `/_synapse/admin/v1/rooms/${encodeURIComponent(
201+
roomID
202+
)}/block`;
203+
return await this.client
204+
.doRequest('GET', endpoint)
205+
.then(
206+
(value) => Value.Decode(BlockStatusResponse, value),
207+
resultifyBotSDKRequestError
208+
);
209+
}
210+
211+
public async getRoomDetails(
212+
roomID: StringRoomID
213+
): Promise<ActionResult<RoomDetailsResponse | undefined>> {
214+
const endpoint = `/_synapse/admin/v1/rooms/${encodeURIComponent(roomID)}`;
215+
return await this.client.doRequest('GET', endpoint).then((value) => {
216+
return Value.Decode(RoomDetailsResponse, value);
217+
}, resultifyBotSDKRequestErrorWith404AsUndefined);
218+
}
180219
}

yarn.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -671,10 +671,10 @@
671671
dependencies:
672672
"@sinonjs/commons" "^3.0.0"
673673

674-
"@the-draupnir-project/matrix-basic-types@^0.2.0":
675-
version "0.2.0"
676-
resolved "https://registry.yarnpkg.com/@the-draupnir-project/matrix-basic-types/-/matrix-basic-types-0.2.0.tgz#21502e4b9c0ad7b1cce077dc7d5ddefd768d0b42"
677-
integrity sha512-plYd/Oi9/JPo+C5ga2D/8L41St+QpdfMwDjE3LuFnjF7TdmxEIw4rx/AFMqZk3KTMbukLQDQDCQF1ZFYmKI6WA==
674+
"@the-draupnir-project/matrix-basic-types@1.3.0":
675+
version "1.3.0"
676+
resolved "https://registry.yarnpkg.com/@the-draupnir-project/matrix-basic-types/-/matrix-basic-types-1.3.0.tgz#02fa9bd75eeed778a57da0844447feca3a4c663c"
677+
integrity sha512-WkUD7cqs9qFr1NQZiJWxa+/1trWrhQqv8SbPxrMLRKv1LMm93ELeguROP470kHNWHrCkVcKmYQVc4uC2Yup+EQ==
678678
dependencies:
679679
"@gnuxie/typescript-result" "^1.0.0"
680680
glob-to-regexp "^0.4.1"
@@ -3164,10 +3164,10 @@ matrix-bot-sdk@>=0.6.4:
31643164
request-promise "^4.2.6"
31653165
sanitize-html "^2.8.0"
31663166

3167-
"matrix-protection-suite@npm:@gnuxie/matrix-protection-suite@2.10.0":
3168-
version "2.10.0"
3169-
resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite/-/matrix-protection-suite-2.10.0.tgz#23016419cbed28b8a645fe9c82b5df2fe5cf4751"
3170-
integrity sha512-Zwrm9RryTgJEqZ6Y9uPa39YZZwsRLUqfS5I5X0fXrre3M68VX7BNIYKQ9xKvjTBKsTI/jSSCJImARYt5fBT0Zg==
3167+
"matrix-protection-suite@npm:@gnuxie/matrix-protection-suite@3.0.0":
3168+
version "3.0.0"
3169+
resolved "https://registry.yarnpkg.com/@gnuxie/matrix-protection-suite/-/matrix-protection-suite-3.0.0.tgz#6e679f1f0e3b9a16f046c095d1ed1416a078e376"
3170+
integrity sha512-X06uzOKN/fwOW2Vm3AKL2HKLbjU1PrqhUsYTzZGGQnEQYkAvoFnuK/TyKF8QaVoMJ/PF4Ybkkj6t5GbZGsGajg==
31713171
dependencies:
31723172
"@gnuxie/typescript-result" "^1.0.0"
31733173
await-lock "^2.2.2"

0 commit comments

Comments
 (0)