Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FOSDEM 2024 hotfix branch #228

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
5 changes: 2 additions & 3 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: Linting and Tests
on:
push:
branches: ["main"]
branches: ["main", "rei/fosdem2024_fire"]
pull_request:

jobs:
Expand Down Expand Up @@ -33,7 +33,7 @@ jobs:
env:
# Only push if this is develop, otherwise we just want to build
# On a PR github.ref is the target branch, so don't push for that either
PUSH: ${{ github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }}
PUSH: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/rei/fosdem2024_fire') && github.event_name != 'pull_request' }}

steps:
- name: Check out
Expand All @@ -55,5 +55,4 @@ jobs:
platforms: ${{ env.PLATFORMS }}
push: ${{ env.PUSH }}
tags: |
ghcr.io/matrix-org/conference-bot:latest
ghcr.io/matrix-org/conference-bot:${{ github.sha }}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"config": "^3.3.3",
"express": "^4.17.1",
"fast-xml-parser": "^4.3.2",
"hls.js": "^0.14.17",
"hls.js": "^1.5.3",
"irc-upd": "^0.11.0",
"js-yaml": "^3.14.1",
"jsrsasign": "^10.1.4",
Expand Down
35 changes: 22 additions & 13 deletions src/Conference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -747,24 +747,33 @@ export class Conference {
return [];
}

public async getInviteTargetsForAuditorium(auditorium: Auditorium, backstage = false): Promise<IPerson[]> {
public async getInviteTargetsForAuditorium(auditorium: Auditorium, roles = [Role.Coordinator, Role.Host, Role.Speaker]): Promise<IPerson[]> {
const people = await this.getPeopleForAuditorium(auditorium);
const roles = [Role.Coordinator, Role.Host, Role.Speaker];
let includesPerson = (person, array) => {
for (const element of array) {
if (element.name === person.name) {
return true
}
}
return false

// HACK dedupe people by name.
const namesToPersons: Map<string, IPerson> = new Map();

let shouldWritePerson = (person: IPerson) => {
// ignore unknown roles
if (! roles.includes(person.role)) return false;

if (! namesToPersons.has(person.name)) return true;

// (TODO HACK we should figure out a nicer way of doing this, like directly tracking multiple roles for people)
// overwrite the previous person entry if this person is a coordinator
// (coordinator role is more important than speaker)
if (person.role == Role.Coordinator) return true;

return false;
};
let uniquePeople: IPerson[] = []

for (const person of people) {
if (!includesPerson(person, uniquePeople)) {
uniquePeople.push(person)
if (shouldWritePerson(person)) {
namesToPersons.set(person.name, person);
}
}
return uniquePeople.filter(p => roles.includes(p.role));

return Array.from(namesToPersons.values());
}

public async getInviteTargetsForTalk(talk: Talk): Promise<IPerson[]> {
Expand Down
10 changes: 10 additions & 0 deletions src/Scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,16 @@ export class Scheduler {
(task.talk.qa_startTime !== null ? `<p>During the talk, you can ask questions here for the Q&A at the end. ` +
`The questions with the most 👍 votes are most visible to the speaker.</p>` : ''),
);

try {
const nameEventContent = await this.client.getRoomStateEvent(confAud.roomId, "m.room.name", "");
if (task.talk.track != '' && task.talk.track != undefined && task.talk.track != nameEventContent["name"]) {
nameEventContent["name"] = task.talk.track;
await this.client.sendStateEvent(confAud.roomId, "m.room.name", "", nameEventContent);
}
} catch (e) {
LogService.error("Scheduler:talkStart", `Error when considering changing name of track room: ${e}`);
}
} else if (task.type === ScheduledTaskType.TalkQA) {
if (!task.talk.prerecorded) return;
if (confTalk !== undefined) {
Expand Down
3 changes: 2 additions & 1 deletion src/backends/penta/PentabarfParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,8 @@ export class PentabarfParser {

const parsedStartTime = simpleTimeParse(pEvent.start);
const parsedDuration = simpleTimeParse(pEvent.duration);
const startTime = moment(dateTs).add(parsedStartTime.hours, 'hours').add(parsedStartTime.minutes, 'minutes');
// HACK FOSDEM2024 +01 TIMEZONE
const startTime = moment(dateTs).add(parsedStartTime.hours - 1, 'hours').add(parsedStartTime.minutes, 'minutes');
const endTime = moment(startTime).add(parsedDuration.hours, 'hours').add(parsedDuration.minutes, 'minutes');
let talk: IPentabarfTalk = {
pretalxCode: pEvent.attr?.["@_code"],
Expand Down
2 changes: 1 addition & 1 deletion src/commands/AttendanceCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class AttendanceCommand implements ICommand {
const doAppend = !!targetAudId && (targetAudId === "all" || targetAudId === await auditorium.getId());
const bs = this.conference.getAuditoriumBackstage(await auditorium.getId());
const inviteTargets = await this.conference.getInviteTargetsForAuditorium(auditorium);
const bsInviteTargets = await this.conference.getInviteTargetsForAuditorium(auditorium, true);
const bsInviteTargets = await this.conference.getInviteTargetsForAuditorium(auditorium);
try {
await append(inviteTargets, bsInviteTargets, await auditorium.getId(), auditorium.roomId, bs.roomId, doAppend);
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/DevCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class DevCommand implements ICommand {
public async run(roomId: string, event: any, args: string[]) {
let people: IPerson[] = [];
for (const aud of this.conference.storedAuditoriums) {
const inviteTargets = await this.conference.getInviteTargetsForAuditorium(aud, true);
const inviteTargets = await this.conference.getInviteTargetsForAuditorium(aud);
people.push(...inviteTargets.filter(i => i.role === Role.Coordinator));
}
const newPeople: IPerson[] = [];
Expand Down
52 changes: 34 additions & 18 deletions src/commands/InviteCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ import { logMessage } from "../LogProxy";
import { IPerson, Role } from "../models/schedule";
import { ConferenceMatrixClient } from "../ConferenceMatrixClient";
import { IConfig } from "../config";
import { sleep } from "../utils";

export class InviteCommand implements ICommand {
public readonly prefixes = ["invite", "inv"];

constructor(private readonly client: ConferenceMatrixClient, private readonly conference: Conference, private readonly config: IConfig) {}

private async createInvites(people: IPerson[], alias: string) {
private async createInvites(people: IPerson[], alias: string): Promise<number> {
const resolved = await resolveIdentifiers(this.client, people);

let targetRoomId;
Expand All @@ -40,7 +41,7 @@ export class InviteCommand implements ICommand {
catch (error) {
throw Error(`Error resolving room id for ${alias}`, {cause: error})
}
await this.ensureInvited(targetRoomId, resolved);
return await this.ensureInvited(targetRoomId, resolved);
}

public async run(managementRoomId: string, event: any, args: string[]) {
Expand All @@ -51,19 +52,20 @@ export class InviteCommand implements ICommand {
// in it. We don't remove anyone and don't care about extras - we just want to make sure
// that a subset of people are joined.

let invitesSent = 0;

if (args[0] && args[0] === "speakers-support") {
let people: IPerson[] = [];
for (const aud of this.conference.storedAuditoriumBackstages) {
people.push(...await this.conference.getInviteTargetsForAuditorium(aud, true));
people.push(...await this.conference.getInviteTargetsForAuditorium(aud, [Role.Speaker]));
}
people = people.filter(p => p.role === Role.Speaker);
const newPeople: IPerson[] = [];
people.forEach(p => {
if (!newPeople.some(n => n.id === p.id)) {
newPeople.push(p);
}
});
await this.createInvites(newPeople, this.config.conference.supportRooms.speakers);
invitesSent += await this.createInvites(newPeople, this.config.conference.supportRooms.speakers);
} else if (args[0] && args[0] === "coordinators-support") {
let people: IPerson[] = [];
for (const aud of this.conference.storedAuditoriums) {
Expand All @@ -75,33 +77,36 @@ export class InviteCommand implements ICommand {
// continue;
// }

const inviteTargets = await this.conference.getInviteTargetsForAuditorium(aud, true);
people.push(...inviteTargets.filter(i => i.role === Role.Coordinator));
const inviteTargets = await this.conference.getInviteTargetsForAuditorium(aud, [Role.Coordinator]);
people.push(...inviteTargets);
}
const newPeople: IPerson[] = [];
people.forEach(p => {
if (!newPeople.some(n => n.id == p.id)) {
newPeople.push(p);
}
});
await this.createInvites(newPeople, this.config.conference.supportRooms.coordinators);
invitesSent += await this.createInvites(newPeople, this.config.conference.supportRooms.coordinators);
} else if (args[0] && args[0] === "si-support") {
const people: IPerson[] = [];
for (const sir of this.conference.storedInterestRooms) {
people.push(...await this.conference.getInviteTargetsForInterest(sir));
}
await this.createInvites(people, this.config.conference.supportRooms.specialInterest);
invitesSent += await this.createInvites(people, this.config.conference.supportRooms.specialInterest);
} else {
await runRoleCommand((_client, room, people) => this.ensureInvited(room, people), this.conference, this.client, managementRoomId, event, args);
await runRoleCommand(async (_client, room, people) => {
invitesSent += await this.ensureInvited(room, people);
}, this.conference, this.client, managementRoomId, event, args);
}

await this.client.sendNotice(managementRoomId, "Invites sent!");
await this.client.sendNotice(managementRoomId, `${invitesSent} invites sent!`);
}

public async ensureInvited(roomId: string, people: ResolvedPersonIdentifier[]) {
public async ensureInvited(roomId: string, people: ResolvedPersonIdentifier[]): Promise<number> {
// We don't want to invite anyone we have already invited or that has joined though, so
// avoid those people. We do this by querying the room state and filtering.
let state;
let invitesSent = 0;
let state: any[];
try {
state = await this.client.getRoomState(roomId);
}
Expand All @@ -114,12 +119,23 @@ export class InviteCommand implements ICommand {
for (const target of people) {
if (target.mxid && effectiveJoinedUserIds.includes(target.mxid)) continue;
if (emailInvitePersonIds.includes(target.person.id)) continue;
try {
await invitePersonToRoom(this.client, target, roomId, this.config);
} catch (e) {
LogService.error("InviteCommand", e);
await logMessage(LogLevel.ERROR, "InviteCommand", `Error inviting ${target.mxid}/${target.emails} / ${target.person.id} to ${roomId} - ignoring: ${e.message ?? e.statusMessage ?? '(see logs)'}`, this.client);
for (let attempt = 0; attempt < 3; ++attempt) {
try {
await invitePersonToRoom(this.client, target, roomId, this.config);
++invitesSent;
} catch (e) {
if (e.statusCode === 429) {
// HACK Retry after ratelimits
await sleep(301_000);
continue;
}
LogService.error("InviteCommand", e);
await logMessage(LogLevel.ERROR, "InviteCommand", `Error inviting ${target.mxid}/${target.emails} / ${target.person.id} to ${roomId} - ignoring: ${e.message ?? e.statusMessage ?? '(see logs)'}`, this.client);
}
break;
}
}

return invitesSent;
}
}
8 changes: 7 additions & 1 deletion src/commands/PermissionsCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
*/

import { ICommand } from "./ICommand";
import { MatrixClient } from "matrix-bot-sdk";
import { LogService, MatrixClient } from "matrix-bot-sdk";
import { Conference } from "../Conference";
import { ResolvedPersonIdentifier } from "../invites";
import { runRoleCommand } from "./actions/roles";
Expand Down Expand Up @@ -49,6 +49,12 @@ export class PermissionsCommand implements ICommand {

for (const person of people) {
if (!person.mxid) continue;

if (! /^@[^:]+:[^\.]+\..+$/.test(person.mxid)) {
LogService.warn("PermissionsCommand", `ignoring invalid MXID ${person.mxid}`);
continue;
}

if (powerLevels['users'][person.mxid]) continue;
powerLevels['users'][person.mxid] = 50;
}
Expand Down
2 changes: 1 addition & 1 deletion src/commands/VerifyCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class VerifyCommand implements ICommand {

if (aud instanceof Auditorium) {
audToInvite = await this.conference.getInviteTargetsForAuditorium(aud);
audBackstageToInvite = await this.conference.getInviteTargetsForAuditorium(aud, true);
audBackstageToInvite = await this.conference.getInviteTargetsForAuditorium(aud);
audToMod = await this.conference.getModeratorsForAuditorium(aud);
} else if (aud instanceof InterestRoom) {
audToInvite = await this.conference.getInviteTargetsForInterest(aud);
Expand Down
4 changes: 2 additions & 2 deletions src/commands/actions/people.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function doAuditoriumResolveAction(
// We know that everyone should be in the backstage room, so resolve that list of people
// to make the identity server lookup efficient.
const backstagePeople = isInvite
? await conference.getInviteTargetsForAuditorium(aud, true)
? await conference.getInviteTargetsForAuditorium(aud)
: await conference.getModeratorsForAuditorium(aud);
LogService.info("backstagePeople", `${backstagePeople}`);
const resolvedBackstagePeople = await resolveIdentifiers(client, backstagePeople);
Expand All @@ -50,7 +50,7 @@ export async function doAuditoriumResolveAction(

const allPossiblePeople = isInvite
? resolvedBackstagePeople
: await resolveIdentifiers(client, await conference.getInviteTargetsForAuditorium(aud, true));
: await resolveIdentifiers(client, await conference.getInviteTargetsForAuditorium(aud));

await action(client, backstage.roomId, resolvedBackstagePeople);

Expand Down
5 changes: 5 additions & 0 deletions src/invites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ export async function resolveIdentifiers(client: ConferenceMatrixClient, people:
return resolved;
}

/**
* Invites a person to a room idempotently.
*
* Raises an exception when we don't have information to invite the user, or there is some Matrix or network error preventing us from doing so.
*/
export async function invitePersonToRoom(client: ConferenceMatrixClient, resolvedPerson: ResolvedPersonIdentifier, roomId: string, config: IConfig): Promise<void> {
if (resolvedPerson.mxid) {
if (config.dry_run_enabled) {
Expand Down
4 changes: 4 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,7 @@ export function jsonReplacerMapToObject(_key: any, input: any): any {
}
return input;
}

export function sleep(millis: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, millis));
}
2 changes: 1 addition & 1 deletion web/hls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import * as Hls from "hls.js/dist/hls.light.js";
import Hls from "hls.js/dist/hls.js";
import { isWidget } from "./widgets";
import { getAttr } from "./common";

Expand Down
18 changes: 5 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2349,7 +2349,7 @@ event-emitter@^0.3.5:
d "1"
es5-ext "~0.10.14"

eventemitter3@^4.0.0, eventemitter3@^4.0.3:
eventemitter3@^4.0.0:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
Expand Down Expand Up @@ -2789,13 +2789,10 @@ he@^1.2.0:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==

hls.js@^0.14.17:
version "0.14.17"
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-0.14.17.tgz#0127cff2ec2f994a54eb955fe669ef6153a8e317"
integrity sha512-25A7+m6qqp6UVkuzUQ//VVh2EEOPYlOBg32ypr34bcPO7liBMOkKFvbjbCBfiPAOTA/7BSx1Dujft3Th57WyFg==
dependencies:
eventemitter3 "^4.0.3"
url-toolkit "^2.1.6"
hls.js@^1.5.3:
version "1.5.3"
resolved "https://registry.yarnpkg.com/hls.js/-/hls.js-1.5.3.tgz#548a4de3b9bdb9f12985cc2f07d206c0f2e74d12"
integrity sha512-gonnYpZ5bxuVdwpcbzfylUlNZ8917LjACUjpWXiaeo8zPAIDfPcMZjEQPy6CeeRSJbcg1P+aVqwxrXr2J+SeUg==

homerunner-client@^0.0.6:
version "0.0.6"
Expand Down Expand Up @@ -5791,11 +5788,6 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"

url-toolkit@^2.1.6:
version "2.2.5"
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.2.5.tgz#58406b18e12c58803e14624df5e374f638b0f607"
integrity sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==

util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
Expand Down
Loading