CI / Build & Quality Checks (pull_request) Successful in 2m58s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 14m30s
createRoom granted org.matrix.msc3401.call.member at user level only for voice rooms. Every other room fell back to Synapse's state_default (50), so in a private group room (where Lotus shows the call button and rings) only admins could start a call; members saw "1 Live · Join" and then "You don't have permission to join!". All new non-space rooms now get call.member: 0 (spaces keep their own override). Verified headless through Create Room → Private: before, no call.member entry (state_default 50); now 0. Existing rooms are unchanged; an admin can set it in room permissions. Fixes #328 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
172 lines
4.2 KiB
TypeScript
172 lines
4.2 KiB
TypeScript
import {
|
|
ICreateRoomOpts,
|
|
ICreateRoomStateEvent,
|
|
JoinRule,
|
|
MatrixClient,
|
|
RestrictedAllowType,
|
|
Room,
|
|
} from 'matrix-js-sdk';
|
|
import { RoomJoinRulesEventContent } from 'matrix-js-sdk/lib/types';
|
|
import { RoomType, StateEvent } from '../../../types/matrix/room';
|
|
import { sendStateEvent } from '../../utils/room';
|
|
import { getViaServers } from '../../plugins/via-servers';
|
|
import { getMxIdServer } from '../../utils/matrix';
|
|
import { CreateRoomAccess } from './types';
|
|
|
|
export const createRoomCreationContent = (
|
|
type: RoomType | undefined,
|
|
allowFederation: boolean,
|
|
additionalCreators: string[] | undefined,
|
|
): object => {
|
|
const content: Record<string, any> = {};
|
|
if (typeof type === 'string') {
|
|
content.type = type;
|
|
}
|
|
if (allowFederation === false) {
|
|
content['m.federate'] = false;
|
|
}
|
|
if (Array.isArray(additionalCreators)) {
|
|
content.additional_creators = additionalCreators;
|
|
}
|
|
|
|
return content;
|
|
};
|
|
|
|
export const createRoomJoinRulesState = (
|
|
access: CreateRoomAccess,
|
|
parent: Room | undefined,
|
|
knock: boolean,
|
|
) => {
|
|
let content: RoomJoinRulesEventContent = {
|
|
join_rule: knock ? JoinRule.Knock : JoinRule.Invite,
|
|
};
|
|
|
|
if (access === CreateRoomAccess.Public) {
|
|
content = {
|
|
join_rule: JoinRule.Public,
|
|
};
|
|
}
|
|
|
|
if (access === CreateRoomAccess.Restricted && parent) {
|
|
content = {
|
|
join_rule: knock ? ('knock_restricted' as JoinRule) : JoinRule.Restricted,
|
|
allow: [
|
|
{
|
|
type: RestrictedAllowType.RoomMembership,
|
|
room_id: parent.roomId,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: StateEvent.RoomJoinRules,
|
|
state_key: '',
|
|
content,
|
|
};
|
|
};
|
|
|
|
export const createRoomParentState = (parent: Room) => ({
|
|
type: StateEvent.SpaceParent,
|
|
state_key: parent.roomId,
|
|
content: {
|
|
canonical: true,
|
|
via: getViaServers(parent),
|
|
},
|
|
});
|
|
|
|
const createSpacePowerLevelsOverride = () => ({
|
|
events_default: 50,
|
|
});
|
|
|
|
export const createRoomEncryptionState = () => ({
|
|
type: 'm.room.encryption',
|
|
state_key: '',
|
|
content: {
|
|
algorithm: 'm.megolm.v1.aes-sha2',
|
|
},
|
|
});
|
|
|
|
export const createRoomCallState = () => ({
|
|
type: 'org.matrix.msc3401.call',
|
|
state_key: '',
|
|
content: {},
|
|
});
|
|
|
|
export const createVoiceRoomPowerLevelsOverride = () => ({
|
|
events: {
|
|
[StateEvent.GroupCallMemberPrefix]: 0,
|
|
},
|
|
});
|
|
|
|
export type CreateRoomData = {
|
|
version: string;
|
|
type?: RoomType;
|
|
parent?: Room;
|
|
access: CreateRoomAccess;
|
|
name: string;
|
|
topic?: string;
|
|
aliasLocalPart?: string;
|
|
encryption?: boolean;
|
|
knock: boolean;
|
|
allowFederation: boolean;
|
|
additionalCreators?: string[];
|
|
};
|
|
export const createRoom = async (mx: MatrixClient, data: CreateRoomData): Promise<string> => {
|
|
const initialState: ICreateRoomStateEvent[] = [];
|
|
|
|
if (data.encryption) {
|
|
initialState.push(createRoomEncryptionState());
|
|
}
|
|
|
|
if (data.parent) {
|
|
initialState.push(createRoomParentState(data.parent));
|
|
}
|
|
|
|
if (data.type === RoomType.Call) {
|
|
initialState.push(createRoomCallState());
|
|
}
|
|
|
|
initialState.push(createRoomJoinRulesState(data.access, data.parent, data.knock));
|
|
|
|
const options: ICreateRoomOpts = {
|
|
room_version: data.version,
|
|
name: data.name,
|
|
topic: data.topic,
|
|
room_alias_name: data.aliasLocalPart,
|
|
creation_content: createRoomCreationContent(
|
|
data.type,
|
|
data.allowFederation,
|
|
data.additionalCreators,
|
|
),
|
|
// [Gitea #328] Every non-space room gets call.member at user level, not just
|
|
// voice rooms: the call button is shown in private group rooms too, and with
|
|
// Synapse's state_default (50) only admins could start or join a call there
|
|
// ("You don't have permission to join!").
|
|
power_level_content_override: createVoiceRoomPowerLevelsOverride(),
|
|
initial_state: initialState,
|
|
};
|
|
|
|
if (data.type === RoomType.Space) {
|
|
options.power_level_content_override = createSpacePowerLevelsOverride();
|
|
}
|
|
|
|
const result = await mx.createRoom(options);
|
|
|
|
if (data.parent) {
|
|
await sendStateEvent(
|
|
mx,
|
|
data.parent.roomId,
|
|
StateEvent.SpaceChild,
|
|
{
|
|
auto_join: false,
|
|
suggested: false,
|
|
via: [getMxIdServer(mx.getUserId() ?? '') ?? ''],
|
|
},
|
|
result.room_id,
|
|
);
|
|
}
|
|
|
|
return result.room_id;
|
|
};
|