diff --git a/src/Conference.ts b/src/Conference.ts index 70d07c1..8f78b21 100644 --- a/src/Conference.ts +++ b/src/Conference.ts @@ -855,8 +855,8 @@ export class Conference { const until = Date.now() + inNextMinutes * 60000; const upcomingTalks: ITalk[] = []; - for (const t of Object.values(this.talks)) { - const talk = await t.getDefinition(); + // Use this.backend.talks because we care about physical talks here too. + for (const talk of this.backend.talks.values()) { const talkEventTime = lambda(talk); // If null is returned then the talk does not have this event, so don't return it as upcoming. if (talkEventTime === null) continue; diff --git a/src/Scheduler.ts b/src/Scheduler.ts index e53e8b0..40de5c7 100644 --- a/src/Scheduler.ts +++ b/src/Scheduler.ts @@ -401,7 +401,8 @@ export class Scheduler { } else { await this.client.sendHtmlText(confAud.roomId, `

The talk will end shortly

`); } - } else if (task.type === ScheduledTaskType.TalkStart1H && confTalk !== undefined) { + } else if (task.type === ScheduledTaskType.TalkStart1H) { + if (confTalk === undefined) return; // This stage is skipped entirely for physical auditoriums' talks, because it only serves to nag // TODO Do we need to ensure that coordinators have checked in? @@ -445,11 +446,15 @@ export class Scheduler { await this.client.sendHtmlText(confTalk.roomId, `

Your talk ends in about 5 minutes

The next talk will start automatically after yours.

`); } await this.client.sendHtmlText(confAud.roomId, `

This talk ends in about 5 minutes

` + (task.talk.qa_startTime !== null ? `

Ask questions here for the speakers!

`: '')); - } else if (task.type === ScheduledTaskType.TalkLivestreamEnd1M && confTalk !== undefined) { + } else if (task.type === ScheduledTaskType.TalkLivestreamEnd1M) { + if (confTalk === undefined) return; await this.client.sendHtmlText(confTalk.roomId, `

Your talk ends in about 1 minute!

The next talk will start automatically after yours. Wrap it up!

`); } else if (task.type === ScheduledTaskType.TalkEnd1M) { + // It's a bit spammy for a physical talk. + if (confTalk === undefined) return; await this.client.sendHtmlText(confAud.roomId, `

This talk ends in about 1 minute!

`); - } else if (task.type === ScheduledTaskType.TalkCheckin45M && confTalk !== undefined) { + } else if (task.type === ScheduledTaskType.TalkCheckin45M) { + if (confTalk === undefined) return; // TODO This is skipped entirely for physical talks, but do we want to ensure coordinators are checked-in? if (!task.talk.prerecorded) return; @@ -486,7 +491,8 @@ export class Scheduler { const resolved = (await resolveIdentifiers(this.client, userIds)).filter(p => p.mxid).map(p => p.mxid!); await this.checkins.expectCheckinFrom(resolved); } - } else if (task.type === ScheduledTaskType.TalkCheckin30M && confTalk !== undefined) { + } else if (task.type === ScheduledTaskType.TalkCheckin30M) { + if (confTalk === undefined) return; // TODO This is skipped entirely for physical talks, but do we want to ensure coordinators are checked-in? if (!task.talk.prerecorded) return; @@ -523,7 +529,8 @@ export class Scheduler { const resolved = (await resolveIdentifiers(this.client, userIds)).filter(p => p.mxid).map(p => p.mxid!); await this.checkins.expectCheckinFrom(resolved); } // else no complaints - } else if (task.type === ScheduledTaskType.TalkCheckin15M && confTalk !== undefined) { + } else if (task.type === ScheduledTaskType.TalkCheckin15M) { + if (confTalk === undefined) return; // TODO This is skipped entirely for physical talks, but do we want to ensure coordinators are checked-in? if (!task.talk.prerecorded) return; diff --git a/src/commands/InviteCommand.ts b/src/commands/InviteCommand.ts index 16bb623..37b5617 100644 --- a/src/commands/InviteCommand.ts +++ b/src/commands/InviteCommand.ts @@ -67,12 +67,13 @@ export class InviteCommand implements ICommand { } else if (args[0] && args[0] === "coordinators-support") { let people: IPerson[] = []; for (const aud of this.conference.storedAuditoriums) { - if (!(await aud.getId()).startsWith("D.")) { + // This hack was not wanted in 2023 or 2024. + // if (!(await aud.getId()).startsWith("D.")) { // HACK: Only invite coordinators for D.* auditoriums. // TODO: Make invitations for support rooms more configurable. // https://github.com/matrix-org/this.conference-bot/issues/76 - continue; - } + // continue; + // } const inviteTargets = await this.conference.getInviteTargetsForAuditorium(aud, true); people.push(...inviteTargets.filter(i => i.role === Role.Coordinator)); diff --git a/src/commands/actions/people.ts b/src/commands/actions/people.ts index b42cd17..0542fe3 100644 --- a/src/commands/actions/people.ts +++ b/src/commands/actions/people.ts @@ -14,13 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -import { LogService, MatrixClient } from "matrix-bot-sdk"; +import { LogLevel, LogService, MatrixClient } from "matrix-bot-sdk"; import { ResolvedPersonIdentifier, resolveIdentifiers } from "../../invites"; import { Auditorium } from "../../models/Auditorium"; import { Conference } from "../../Conference"; import { asyncFilter } from "../../utils"; import { InterestRoom } from "../../models/InterestRoom"; import { ConferenceMatrixClient } from "../../ConferenceMatrixClient"; +import { logMessage } from "../../LogProxy"; export interface IAction { (client: MatrixClient, roomId: string, people: ResolvedPersonIdentifier[]): Promise; @@ -60,9 +61,12 @@ export async function doAuditoriumResolveAction( ? await conference.getInviteTargetsForAuditorium(realAud) : await conference.getModeratorsForAuditorium(realAud); const resolvedAudPeople = audPeople.map(p => allPossiblePeople.find(b => p.id === b.person.id)); - if (resolvedAudPeople.some(p => !p)) throw new Error(`Failed to resolve all targets for auditorium ${audId}`); + if (resolvedAudPeople.some(p => !p)) { + logMessage(LogLevel.WARN, "people", `Failed to resolve all targets for auditorium ${audId}. Inviting others anyway.`, client); + } - await action(client, realAud.roomId, resolvedAudPeople as ResolvedPersonIdentifier[]); + const resolvedAudPeopleOnly = resolvedAudPeople.filter(p => !!p); + await action(client, realAud.roomId, resolvedAudPeopleOnly as ResolvedPersonIdentifier[]); if (!skipTalks) { const talks = await asyncFilter( @@ -80,10 +84,11 @@ export async function doAuditoriumResolveAction( const unresolveable = talkPeople.filter( p => allPossiblePeople.find(b => p.id === b.person.id) === undefined ) - throw new Error(`Failed to resolve all targets for talk ${await talk.getId()}: ` + JSON.stringify(unresolveable)); + logMessage(LogLevel.WARN, "people", `Failed to resolve all targets for talk ${await talk.getId()}: ` + JSON.stringify(unresolveable), client); } - await action(client, talk.roomId, resolvedTalkPeople as ResolvedPersonIdentifier[]); + const resolvedTalkPeopleOnly = resolvedTalkPeople.filter(p => !!p); + await action(client, talk.roomId, resolvedTalkPeopleOnly as ResolvedPersonIdentifier[]); } } } diff --git a/src/web.ts b/src/web.ts index 691631f..dc91c6e 100644 --- a/src/web.ts +++ b/src/web.ts @@ -37,9 +37,16 @@ export function renderAuditoriumWidget(req: Request, res: Response, conference: return res.sendStatus(404); } + //let sid = audId.toLowerCase().replace(/[^a-z0-9]/g, ''); + + // HACK for FOSDEM 2023 and FOSDEM 2024: transform auditorium IDs to the livestream ID + // 1. 'K1.105A (Words)' -> 'k1.105a' + // 2. 'k1.105a' -> 'k1105a' + let sid = audId.toLowerCase().replace(/\s+\(.+\)$/, '').replace(/[^a-z0-9]/g, ''); + const streamUrl = template(auditoriumUrl, { id: audId.toLowerCase(), - sId: audId.toLowerCase().replace(/[^a-z0-9]/g, ''), + sId: sid }); return res.render('auditorium.liquid', {