mirror of
https://github.com/the-draupnir-project/Draupnir.git
synced 2026-10-26 02:42:21 +00:00
Add RoomTakedownCapability and SynapseAdmin implementation.
This commit is contained in:
5 files changed
+236
-5
No files matched your search
@@ -0,0 +1,45 @@
|
||||
// SPDX-FileCopyrightText: 2025 Gnuxie <Gnuxie@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Result } from "@gnuxie/typescript-result";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import {
|
||||
StringRoomID,
|
||||
StringUserID,
|
||||
} from "@the-draupnir-project/matrix-basic-types";
|
||||
import {
|
||||
Capability,
|
||||
CapabilityMethodSchema,
|
||||
describeCapabilityInterface,
|
||||
} from "matrix-protection-suite";
|
||||
|
||||
export type RoomTakedownDetails = Partial<{
|
||||
creator: StringUserID | undefined;
|
||||
room_id: StringRoomID;
|
||||
name: string | undefined;
|
||||
topic: string | undefined;
|
||||
avatar: string | undefined;
|
||||
}>;
|
||||
|
||||
export const RoomTakedownCapability = Type.Intersect([
|
||||
Type.Object({
|
||||
isRoomTakendown: CapabilityMethodSchema,
|
||||
takedownRoom: CapabilityMethodSchema,
|
||||
}),
|
||||
Capability,
|
||||
]);
|
||||
// we'll probably want to include room details too so we can provide some
|
||||
// rudinemtary information to takedown.
|
||||
// I'd make the fields optional however because it may be impossible
|
||||
// to get those details on conduwuit.
|
||||
export type RoomTakedownCapability = {
|
||||
isRoomTakendown(roomID: StringRoomID): Promise<Result<boolean>>;
|
||||
takedownRoom(roomID: StringRoomID): Promise<Result<RoomTakedownDetails>>;
|
||||
} & Capability;
|
||||
|
||||
describeCapabilityInterface({
|
||||
name: "RoomTakedownCapability",
|
||||
description: "Capability that targets matrix rooms",
|
||||
schema: RoomTakedownCapability,
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: 2025 Gnuxie <Gnuxie@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
describeCapabilityRenderer,
|
||||
DescriptionMeta,
|
||||
} from "matrix-protection-suite";
|
||||
import {
|
||||
RoomTakedownCapability,
|
||||
RoomTakedownDetails,
|
||||
} from "./RoomTakedownCapability";
|
||||
import { RendererMessageCollector } from "./RendererMessageCollector";
|
||||
import {
|
||||
MatrixRoomReference,
|
||||
StringRoomID,
|
||||
} from "@the-draupnir-project/matrix-basic-types";
|
||||
import { isError, Result } from "@gnuxie/typescript-result";
|
||||
import {
|
||||
DeadDocumentJSX,
|
||||
DocumentNode,
|
||||
} from "@the-draupnir-project/interface-manager";
|
||||
import { renderFailedSingularConsequence } from "./CommonRenderers";
|
||||
import { renderRoomPill } from "../commands/interface-manager/MatrixHelpRenderer";
|
||||
import { Draupnir } from "../Draupnir";
|
||||
|
||||
function renderCodeOrDefault(
|
||||
item: string | undefined,
|
||||
defaultText: string
|
||||
): DocumentNode {
|
||||
return item === undefined ? (
|
||||
<fragment>{defaultText}</fragment>
|
||||
) : (
|
||||
<code>{item}</code>
|
||||
);
|
||||
}
|
||||
|
||||
function renderTakedown(
|
||||
roomID: StringRoomID,
|
||||
details: RoomTakedownDetails
|
||||
): DocumentNode {
|
||||
return (
|
||||
<details>
|
||||
<summary>
|
||||
Successfully takendown the room{" "}
|
||||
{renderRoomPill(MatrixRoomReference.fromRoomID(roomID))}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>name: {renderCodeOrDefault(details.name, "no name available")}</li>
|
||||
<li>
|
||||
creator:{" "}
|
||||
{renderCodeOrDefault(
|
||||
details.creator,
|
||||
"creator information unavailable"
|
||||
)}
|
||||
</li>
|
||||
<li>
|
||||
topic: {renderCodeOrDefault(details.topic, "topic unavailable")}
|
||||
</li>
|
||||
</ul>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
|
||||
class StandardRoomTakedownCapabilityRenderer implements RoomTakedownCapability {
|
||||
public readonly requiredEventPermissions =
|
||||
this.capability.requiredEventPermissions;
|
||||
public readonly requiredPermissions = this.capability.requiredPermissions;
|
||||
public readonly requiredStatePermissions =
|
||||
this.capability.requiredStatePermissions;
|
||||
constructor(
|
||||
private readonly description: DescriptionMeta,
|
||||
private readonly messageCollector: RendererMessageCollector,
|
||||
private readonly capability: RoomTakedownCapability
|
||||
) {
|
||||
// nothing to do.
|
||||
}
|
||||
|
||||
public async isRoomTakendown(roomID: StringRoomID): Promise<Result<boolean>> {
|
||||
// pass this one through without rendering because it doesn't really have a consequence
|
||||
return await this.capability.isRoomTakendown(roomID);
|
||||
}
|
||||
|
||||
public async takedownRoom(
|
||||
roomID: StringRoomID
|
||||
): Promise<Result<RoomTakedownDetails>> {
|
||||
const capabilityResult = await this.capability.takedownRoom(roomID);
|
||||
if (isError(capabilityResult)) {
|
||||
this.messageCollector.addOneliner(
|
||||
this.description,
|
||||
this.capability,
|
||||
renderFailedSingularConsequence(
|
||||
this.description,
|
||||
<span>
|
||||
Failed to takedown room{" "}
|
||||
{renderRoomPill(MatrixRoomReference.fromRoomID(roomID))}
|
||||
</span>,
|
||||
capabilityResult.error
|
||||
)
|
||||
);
|
||||
return capabilityResult;
|
||||
}
|
||||
this.messageCollector.addMessage(
|
||||
this.description,
|
||||
this.capability,
|
||||
renderTakedown(roomID, capabilityResult.ok)
|
||||
);
|
||||
return capabilityResult;
|
||||
}
|
||||
}
|
||||
|
||||
describeCapabilityRenderer<RoomTakedownCapability, Draupnir>({
|
||||
name: "StandardRoomTakedownCapabilityRenderer",
|
||||
description: "Renders the standard room takedown capability result",
|
||||
interface: "RoomTakedownCapability",
|
||||
isDefaultForInterface: true,
|
||||
factory(description, draupnir, capability) {
|
||||
return new StandardRoomTakedownCapabilityRenderer(
|
||||
description,
|
||||
draupnir.capabilityMessageRenderer,
|
||||
capability
|
||||
);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: 2025 Gnuxie <Gnuxie@protonmail.com>
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { StringRoomID } from "@the-draupnir-project/matrix-basic-types";
|
||||
import { Logger } from "matrix-protection-suite";
|
||||
import {
|
||||
RoomTakedownCapability,
|
||||
RoomTakedownDetails,
|
||||
} from "../RoomTakedownCapability";
|
||||
import { SynapseAdminClient } from "matrix-protection-suite-for-matrix-bot-sdk";
|
||||
import { isError, Ok, Result } from "@gnuxie/typescript-result";
|
||||
|
||||
const log = new Logger("SynapseAdminRoomTakedownCapability");
|
||||
|
||||
export class SynapseAdminRoomTakedownCapability
|
||||
implements RoomTakedownCapability
|
||||
{
|
||||
public readonly requiredPermissions = [];
|
||||
public readonly requiredStatePermissions = [];
|
||||
public readonly requiredEventPermissions = [];
|
||||
public constructor(private readonly adminClient: SynapseAdminClient) {
|
||||
// nothing to do mare.
|
||||
}
|
||||
public async isRoomTakendown(roomID: StringRoomID): Promise<Result<boolean>> {
|
||||
const blockStatusResponse = await this.adminClient.getBlockStatus(roomID);
|
||||
if (isError(blockStatusResponse)) {
|
||||
return blockStatusResponse;
|
||||
} else {
|
||||
return Ok(blockStatusResponse.ok.block);
|
||||
}
|
||||
}
|
||||
|
||||
public async takedownRoom(
|
||||
roomID: StringRoomID
|
||||
): Promise<Result<RoomTakedownDetails>> {
|
||||
const detailsResponse = await this.adminClient.getRoomDetails(roomID);
|
||||
let details: RoomTakedownDetails;
|
||||
if (isError(detailsResponse)) {
|
||||
log.warn(
|
||||
"Unable to fetch details for a room being requested to shutdown",
|
||||
detailsResponse.error
|
||||
);
|
||||
details = { room_id: roomID };
|
||||
} else {
|
||||
details = {
|
||||
name: detailsResponse.ok?.name ?? undefined,
|
||||
creator: detailsResponse.ok?.creator,
|
||||
avatar: detailsResponse.ok?.avatar ?? undefined,
|
||||
topic: detailsResponse.ok?.topic ?? undefined,
|
||||
room_id: roomID,
|
||||
};
|
||||
}
|
||||
const takedownResult = await this.adminClient.deleteRoom(roomID, {
|
||||
block: true,
|
||||
purge: true,
|
||||
});
|
||||
if (isError(takedownResult)) {
|
||||
return takedownResult;
|
||||
} else {
|
||||
return Ok(details);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,3 +11,5 @@
|
||||
import "./StandardEventConsequencesRenderer";
|
||||
import "./ServerACLConsequencesRenderer";
|
||||
import "./StandardUserConsequencesRenderer";
|
||||
import "./RoomTakedownCapability";
|
||||
import "./RoomTakedownCapabilityRenderer";
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "matrix-protection-suite";
|
||||
import { RoomAuditLog } from "./RoomAuditLog";
|
||||
import { isError, Ok, Result, ResultError } from "@gnuxie/typescript-result";
|
||||
import { RoomTakedownCapability } from "../../capabilities/RoomTakedownCapability";
|
||||
|
||||
const log = new Logger("RoomTakedown");
|
||||
|
||||
@@ -28,11 +29,6 @@ type RoomTakedown = {
|
||||
checkAllRooms(revision: PolicyListRevision): Promise<Result<void>>;
|
||||
};
|
||||
|
||||
export type RoomTakedownCapability = {
|
||||
isRoomTakendown(roomID: StringRoomID): Promise<Result<boolean>>;
|
||||
takedownRoom(roomID: StringRoomID): Promise<Result<void>>;
|
||||
};
|
||||
|
||||
/**
|
||||
* This exists as the main handler for reacting to literal room policies
|
||||
* (that have been reversed from hashed policies elsewhere) AND
|
||||
|
||||
Reference in new issue
Block a user