Skip to content

Commit da08432

Browse files
authored
Refactor list creation with MSC3784 support. (#386)
matrix-org/matrix-spec-proposals#3784 This was extracted from the appservice mjolnir work to reduce review burden.
1 parent 5bd23ce commit da08432

File tree

3 files changed

+99
-29
lines changed

3 files changed

+99
-29
lines changed

src/commands/CreateBanListCommand.ts

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,42 +15,20 @@ limitations under the License.
1515
*/
1616

1717
import { Mjolnir } from "../Mjolnir";
18-
import { SHORTCODE_EVENT_TYPE } from "../models/PolicyList";
18+
import PolicyList from "../models/PolicyList";
1919
import { Permalinks, RichReply } from "matrix-bot-sdk";
2020

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

26-
const powerLevels: { [key: string]: any } = {
27-
"ban": 50,
28-
"events": {
29-
"m.room.name": 100,
30-
"m.room.power_levels": 100,
31-
},
32-
"events_default": 50, // non-default
33-
"invite": 0,
34-
"kick": 50,
35-
"notifications": {
36-
"room": 20,
37-
},
38-
"redact": 50,
39-
"state_default": 50,
40-
"users": {
41-
[await mjolnir.client.getUserId()]: 100,
42-
[event["sender"]]: 50
43-
},
44-
"users_default": 0,
45-
};
46-
47-
const listRoomId = await mjolnir.client.createRoom({
48-
preset: "public_chat",
49-
room_alias_name: aliasLocalpart,
50-
invite: [event['sender']],
51-
initial_state: [{ type: SHORTCODE_EVENT_TYPE, state_key: "", content: { shortcode: shortcode } }],
52-
power_level_content_override: powerLevels,
53-
});
26+
const listRoomId = await PolicyList.createList(
27+
mjolnir.client,
28+
shortcode,
29+
[event['sender']],
30+
{ room_alias_name: aliasLocalpart }
31+
);
5432

5533
const roomRef = Permalinks.forRoom(listRoomId);
5634
await mjolnir.watchList(roomRef);

src/models/PolicyList.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ class PolicyList extends EventEmitter {
8686
// Events that we have already informed the batcher about, that we haven't loaded from the room state yet.
8787
private batchedEvents = new Set<string /* event id */>();
8888

89+
/** MSC3784 support. Please note that policy lists predate room types. So there will be lists in the wild without this type. */
90+
public static readonly ROOM_TYPE = "support.feline.policy.lists.msc.v1";
91+
public static readonly ROOM_TYPE_VARIANTS = [PolicyList.ROOM_TYPE]
92+
8993
/**
9094
* This is used to annotate state events we store with the rule they are associated with.
9195
* If we refactor this, it is important to also refactor any listeners to 'PolicyList.update'
@@ -105,6 +109,65 @@ class PolicyList extends EventEmitter {
105109
this.batcher = new UpdateBatcher(this);
106110
}
107111

112+
/**
113+
* Create a new policy list.
114+
* @param client A MatrixClient that will be used to create the list.
115+
* @param shortcode A shortcode to refer to the list with.
116+
* @param invite A list of users to invite to the list and make moderator.
117+
* @param createRoomOptions Additional room create options such as an alias.
118+
* @returns The room id for the newly created policy list.
119+
*/
120+
public static async createList(
121+
client: MatrixClient,
122+
shortcode: string,
123+
invite: string[],
124+
createRoomOptions = {}
125+
): Promise<string /* room id */> {
126+
const powerLevels: { [key: string]: any } = {
127+
"ban": 50,
128+
"events": {
129+
"m.room.name": 100,
130+
"m.room.power_levels": 100,
131+
},
132+
"events_default": 50, // non-default
133+
"invite": 0,
134+
"kick": 50,
135+
"notifications": {
136+
"room": 20,
137+
},
138+
"redact": 50,
139+
"state_default": 50,
140+
"users": {
141+
[await client.getUserId()]: 100,
142+
...invite.reduce((users, mxid) => ({...users, [mxid]: 50 }), {}),
143+
},
144+
"users_default": 0,
145+
};
146+
const finalRoomCreateOptions = {
147+
// Support for MSC3784.
148+
creation_content: {
149+
type: PolicyList.ROOM_TYPE
150+
},
151+
preset: "public_chat",
152+
invite,
153+
initial_state: [
154+
{
155+
type: SHORTCODE_EVENT_TYPE,
156+
state_key: "",
157+
content: {shortcode: shortcode}
158+
}
159+
],
160+
power_level_content_override: powerLevels,
161+
...createRoomOptions
162+
};
163+
// Guard room type in case someone overwrites it when declaring custom creation_content in future code.
164+
if (!PolicyList.ROOM_TYPE_VARIANTS.includes(finalRoomCreateOptions.creation_content.type)) {
165+
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.`);
166+
}
167+
const listRoomId = await client.createRoom(finalRoomCreateOptions);
168+
return listRoomId
169+
}
170+
108171
/**
109172
* The code that can be used to refer to this banlist in Mjolnir commands.
110173
*/

test/integration/banListTest.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getMessagesByUserIn } from "../../src/utils";
88
import { Mjolnir } from "../../src/Mjolnir";
99
import { ALL_RULE_TYPES, Recommendation, RULE_SERVER, RULE_USER, SERVER_RULE_TYPES } from "../../src/models/ListRule";
1010
import AccessControlUnit, { Access, EntityAccess } from "../../src/models/AccessControlUnit";
11+
import { randomUUID } from "crypto";
1112

1213
/**
1314
* Create a policy rule in a policy room.
@@ -564,3 +565,31 @@ describe('Test: AccessControlUnit interaction with policy lists.', function() {
564565
assertAccess(Access.Allowed, aclUnit.getAccessForServer(banMeServer), "Should not longer be any rules");
565566
})
566567
})
568+
569+
describe('Test: Creating policy lists.', function() {
570+
it('Will automatically invite and op users from invites', async function() {
571+
const mjolnir: Mjolnir = this.mjolnir;
572+
const testUsers = await Promise.all([...Array(2)].map(_ => newTestUser(this.config.homeserverUrl, { name: { contains: "moderator" } })))
573+
const invite = await Promise.all(testUsers.map(client => client.getUserId()));
574+
const policyListId = await PolicyList.createList(
575+
mjolnir.client,
576+
randomUUID(),
577+
invite
578+
);
579+
// Check power levels are right.
580+
const powerLevelEvent = await mjolnir.client.getRoomStateEvent(policyListId, "m.room.power_levels", "");
581+
assert.equal(Object.keys(powerLevelEvent.users ?? {}).length, invite.length + 1);
582+
// Check create event for MSC3784 support.
583+
const createEvent = await mjolnir.client.getRoomStateEvent(policyListId, "m.room.create", "");
584+
assert.equal(createEvent.type, PolicyList.ROOM_TYPE);
585+
// We can't create rooms without forgetting the type.
586+
await assert.rejects(
587+
async () => {
588+
await PolicyList.createList(mjolnir.client, randomUUID(), [], {
589+
creation_content: {}
590+
})
591+
},
592+
TypeError
593+
);
594+
})
595+
})

0 commit comments

Comments
 (0)