Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 7 additions & 29 deletions src/commands/CreateBanListCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,42 +15,20 @@ limitations under the License.
*/

import { Mjolnir } from "../Mjolnir";
import { SHORTCODE_EVENT_TYPE } from "../models/PolicyList";
import PolicyList from "../models/PolicyList";
import { Permalinks, RichReply } from "matrix-bot-sdk";

// !mjolnir list create <shortcode> <alias localpart>
export async function execCreateListCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const shortcode = parts[3];
const aliasLocalpart = parts[4];

const powerLevels: { [key: string]: any } = {
"ban": 50,
"events": {
"m.room.name": 100,
"m.room.power_levels": 100,
},
"events_default": 50, // non-default
"invite": 0,
"kick": 50,
"notifications": {
"room": 20,
},
"redact": 50,
"state_default": 50,
"users": {
[await mjolnir.client.getUserId()]: 100,
[event["sender"]]: 50
},
"users_default": 0,
};

const listRoomId = await mjolnir.client.createRoom({
preset: "public_chat",
room_alias_name: aliasLocalpart,
invite: [event['sender']],
initial_state: [{ type: SHORTCODE_EVENT_TYPE, state_key: "", content: { shortcode: shortcode } }],
power_level_content_override: powerLevels,
});
const listRoomId = await PolicyList.createList(
mjolnir.client,
shortcode,
[event['sender']],
{ room_alias_name: aliasLocalpart }
);

const roomRef = Permalinks.forRoom(listRoomId);
await mjolnir.watchList(roomRef);
Expand Down
63 changes: 63 additions & 0 deletions src/models/PolicyList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ class PolicyList extends EventEmitter {
// Events that we have already informed the batcher about, that we haven't loaded from the room state yet.
private batchedEvents = new Set<string /* event id */>();

/** MSC3784 support. Please note that policy lists predate room types. So there will be lists in the wild without this type. */
public static readonly ROOM_TYPE = "support.feline.policy.lists.msc.v1";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

support.feline.policy.lists.msc.v1? I assume that this was created for this occasion? We typically use org.matrix.mjolnir.XXX for Mjölnir-specific prefixes, no?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't my MSC, so we don't have a choice in the matter

Copy link
Copy Markdown

@FSG-Cat FSG-Cat Oct 19, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the unstable room type for MSC3784.

Edit: And I happen to be the author that picked the unstable identifier.

public static readonly ROOM_TYPE_VARIANTS = [PolicyList.ROOM_TYPE]

/**
* This is used to annotate state events we store with the rule they are associated with.
* If we refactor this, it is important to also refactor any listeners to 'PolicyList.update'
Expand All @@ -105,6 +109,65 @@ class PolicyList extends EventEmitter {
this.batcher = new UpdateBatcher(this);
}

/**
* Create a new policy list.
* @param client A MatrixClient that will be used to create the list.
* @param shortcode A shortcode to refer to the list with.
* @param invite A list of users to invite to the list and make moderator.
* @param createRoomOptions Additional room create options such as an alias.
* @returns The room id for the newly created policy list.
*/
public static async createList(
client: MatrixClient,
shortcode: string,
invite: string[],
createRoomOptions = {}
): Promise<string /* room id */> {
const powerLevels: { [key: string]: any } = {
"ban": 50,
"events": {
"m.room.name": 100,
"m.room.power_levels": 100,
},
"events_default": 50, // non-default
"invite": 0,
"kick": 50,
"notifications": {
"room": 20,
},
"redact": 50,
"state_default": 50,
"users": {
[await client.getUserId()]: 100,
...invite.reduce((users, mxid) => ({...users, [mxid]: 50 }), {}),
},
"users_default": 0,
};
const finalRoomCreateOptions = {
// Support for MSC3784.
creation_content: {
type: PolicyList.ROOM_TYPE
},
preset: "public_chat",
invite,
initial_state: [
{
type: SHORTCODE_EVENT_TYPE,
state_key: "",
content: {shortcode: shortcode}
}
],
power_level_content_override: powerLevels,
...createRoomOptions
};
// Guard room type in case someone overwrites it when declaring custom creation_content in future code.
if (!PolicyList.ROOM_TYPE_VARIANTS.includes(finalRoomCreateOptions.creation_content.type)) {
throw new TypeError(`Creating a policy room with a type other than the policy room type is not supported, you probably don't want to do this.`);
}
const listRoomId = await client.createRoom(finalRoomCreateOptions);
return listRoomId
}

/**
* The code that can be used to refer to this banlist in Mjolnir commands.
*/
Expand Down
29 changes: 29 additions & 0 deletions test/integration/banListTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getMessagesByUserIn } from "../../src/utils";
import { Mjolnir } from "../../src/Mjolnir";
import { ALL_RULE_TYPES, Recommendation, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES } from "../../src/models/ListRule";
import AccessControlUnit, { Access, EntityAccess } from "../../src/models/AccessControlUnit";
import { randomUUID } from "crypto";

/**
* Create a policy rule in a policy room.
Expand Down Expand Up @@ -564,3 +565,31 @@ describe('Test: AccessControlUnit interaction with policy lists.', function() {
assertAccess(Access.Allowed, aclUnit.getAccessForServer(banMeServer), "Should not longer be any rules");
})
})

describe('Test: Creating policy lists.', function() {
it('Will automatically invite and op users from invites', async function() {
const mjolnir: Mjolnir = this.mjolnir;
const testUsers = await Promise.all([...Array(2)].map(_ => newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } })))
const invite = await Promise.all(testUsers.map(client => client.getUserId()));
const policyListId = await PolicyList.createList(
mjolnir.client,
randomUUID(),
invite
);
// Check power levels are right.
const powerLevelEvent = await mjolnir.client.getRoomStateEvent(policyListId, "m.room.power_levels", "");
assert.equal(Object.keys(powerLevelEvent.users ?? {}).length, invite.length + 1);
// Check create event for MSC3784 support.
const createEvent = await mjolnir.client.getRoomStateEvent(policyListId, "m.room.create", "");
assert.equal(createEvent.type, PolicyList.ROOM_TYPE);
// We can't create rooms without forgetting the type.
await assert.rejects(
async () => {
await PolicyList.createList(mjolnir.client, randomUUID(), [], {
creation_content: {}
})
},
TypeError
);
})
})