Skip to content

Commit f7853e1

Browse files
committed
feature(events): extended event list to support directories, save events by their id
This commit adds the ability to add events sorted by directories generated by https://github.com/Malex14/hio_timetable_extractor. Each directory consists of a name and potentially containing events and/or subdirectories. Events are stored in the userconfig by their id. The event's name is now being saved in the corresponding eventDetails. The filter functionality has been updated to support directories as well.
1 parent f241c4f commit f7853e1

File tree

13 files changed

+322
-185
lines changed

13 files changed

+322
-185
lines changed

source/lib/all-events.ts

Lines changed: 93 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,106 @@
1-
import {readFile} from 'node:fs/promises';
1+
import {readFile, watch} from 'node:fs/promises';
2+
import type {
3+
EventDirectory, EventId, Events,
4+
} from './types.ts';
25

3-
async function getAll(): Promise<string[]> {
4-
const data = await readFile('eventfiles/all.txt', 'utf8');
5-
const list = data.split('\n').filter(element => element !== '');
6-
return list;
6+
const DIRECTORY_FILE = 'eventfiles/directory.json';
7+
8+
const directory = await loadDirectory();
9+
const namesOfEvents: Record<string, string> = await generateMapping();
10+
11+
async function watchForDirectoryChanges() {
12+
const watcher = watch(DIRECTORY_FILE);
13+
for await (const event of watcher) {
14+
console.log(event);
15+
if (event.eventType === 'change') {
16+
await loadDirectory();
17+
await generateMapping();
18+
}
19+
}
720
}
821

9-
export async function count(): Promise<number> {
10-
const allEvents = await getAll();
11-
return allEvents.length;
22+
await watchForDirectoryChanges();
23+
24+
async function loadDirectory(): Promise<Partial<EventDirectory>> {
25+
const directoryString = await readFile(DIRECTORY_FILE);
26+
const directory = JSON.parse(directoryString.toString()) as Partial<EventDirectory>;
27+
return directory;
1228
}
1329

14-
export async function exists(name: string): Promise<boolean> {
15-
const allEvents = await getAll();
16-
return allEvents.includes(name);
30+
async function generateMapping(): Promise<Record<string, string>> {
31+
const namesOfEvents: Record<string, string> = {};
32+
33+
function collect(directory: Partial<EventDirectory>) {
34+
for (const subDirectory of Object.values(directory.subDirectories ?? {})) {
35+
collect(subDirectory);
36+
}
37+
38+
Object.assign(namesOfEvents, directory.events ?? {});
39+
}
40+
41+
collect(directory);
42+
43+
return namesOfEvents;
1744
}
1845

19-
export async function nonExisting(names: readonly string[]): Promise<string[]> {
20-
const allEvents = new Set(await getAll());
21-
const result: string[] = [];
22-
for (const event of names) {
23-
if (!allEvents.has(event)) {
24-
result.push(event);
46+
function resolvePath(path: string[]): Partial<EventDirectory> {
47+
let resolvedDirectory = directory;
48+
49+
for (const part of path) {
50+
if (resolvedDirectory.subDirectories === undefined || !(part in resolvedDirectory.subDirectories)) {
51+
throw new Error('Ungültiger Pfad');
2552
}
53+
54+
resolvedDirectory = resolvedDirectory.subDirectories[part]!;
2655
}
2756

28-
return result;
57+
return resolvedDirectory;
2958
}
3059

31-
export async function find(
32-
pattern: string | RegExp,
33-
ignore: readonly string[] = [],
34-
): Promise<readonly string[]> {
35-
const allEvents = await getAll();
36-
const regex = new RegExp(pattern, 'i');
37-
const filtered = allEvents.filter(event =>
38-
regex.test(event) && !ignore.includes(event));
39-
return filtered;
60+
export function getEventName(id: EventId): string {
61+
return namesOfEvents[id] ?? id;
62+
}
63+
64+
export function count(): number {
65+
return Object.keys(namesOfEvents).length;
66+
}
67+
68+
export function nonExisting(ids: readonly EventId[]): readonly EventId[] {
69+
return ids.filter(id => !(id in namesOfEvents));
70+
}
71+
72+
export function find(
73+
pattern: string | RegExp | undefined,
74+
startAt: string[] = [],
75+
): Readonly<EventDirectory> {
76+
if (pattern !== undefined) {
77+
const regex = new RegExp(pattern, 'i');
78+
const accumulator: Events = {};
79+
80+
function collect(directory: Partial<EventDirectory>) {
81+
for (const [eventId, name] of Object.entries(directory.events ?? {})) {
82+
if (regex.test(name)) {
83+
accumulator[eventId as EventId] = name;
84+
}
85+
}
86+
87+
for (const subDirectory of Object.values(directory.subDirectories ?? {})) {
88+
collect(subDirectory);
89+
}
90+
}
91+
92+
collect(resolvePath(startAt));
93+
94+
return {
95+
subDirectories: {},
96+
events: Object.fromEntries(Object.entries(accumulator).sort((a, b) => a[1].localeCompare(b[1]))),
97+
};
98+
}
99+
100+
const directory = resolvePath(startAt);
101+
102+
return {
103+
subDirectories: directory.subDirectories ?? {},
104+
events: directory.events ?? {},
105+
};
40106
}

source/lib/calendar-helper.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type {MyContext, Userconfig} from './types.ts';
1+
import type {EventId, MyContext, Userconfig} from './types.ts';
22

33
export function getUrl(id: number, userconfig: Userconfig): string {
44
let filename = `${id}`;
@@ -14,3 +14,7 @@ export function getUrl(id: number, userconfig: Userconfig): string {
1414
export function getUrlFromContext(ctx: MyContext): string {
1515
return getUrl(ctx.from!.id, ctx.userconfig.mine);
1616
}
17+
18+
export function getUserEventIdsFromContext(ctx: MyContext): EventId[] {
19+
return Object.keys(ctx.userconfig.mine.events) as EventId[];
20+
}

source/lib/change-helper.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import {readFile} from 'node:fs/promises';
22
import {html as format} from 'telegram-format';
3-
import type {Change, EventEntry, NaiveDateTime} from './types.ts';
3+
import type {
4+
Change, EventEntry, EventId, NaiveDateTime,
5+
} from './types.ts';
6+
import {getEventName} from './all-events.js';
47

58
export function generateChangeDescription(change: Change): string {
69
let text = '';
@@ -28,11 +31,11 @@ export function generateChangeDescription(change: Change): string {
2831
}
2932

3033
export function generateChangeText(
31-
name: string,
34+
eventId: EventId,
3235
date: NaiveDateTime | undefined,
3336
change: Change,
3437
): string {
35-
let text = generateChangeTextHeader(name, date);
38+
let text = generateChangeTextHeader(eventId, date);
3639

3740
if (Object.keys(change).length > 2) {
3841
text += '\nÄnderungen:\n';
@@ -43,13 +46,13 @@ export function generateChangeText(
4346
}
4447

4548
export function generateChangeTextHeader(
46-
name: string,
49+
eventId: EventId,
4750
date: NaiveDateTime | undefined,
4851
): string {
4952
let text = '';
5053
text += format.bold('Veranstaltungsänderung');
5154
text += '\n';
52-
text += format.bold(format.escape(name));
55+
text += format.bold(format.escape(getEventName(eventId)));
5356
if (date) {
5457
text += ` ${date}`;
5558
}
@@ -59,16 +62,15 @@ export function generateChangeTextHeader(
5962
}
6063

6164
export function generateShortChangeText(
62-
name: string,
65+
eventId: EventId,
6366
date: NaiveDateTime,
6467
): string {
65-
return `${name} ${date}`;
68+
return `${getEventName(eventId)} ${date}`;
6669
}
6770

68-
export async function loadEvents(eventname: string): Promise<EventEntry[]> {
71+
export async function loadEvents(eventId: EventId): Promise<EventEntry[]> {
6972
try {
70-
const filename = eventname.replaceAll('/', '-');
71-
const content = await readFile(`eventfiles/${filename}.json`, 'utf8');
73+
const content = await readFile(`eventfiles/${eventId}.json`, 'utf8');
7274
return JSON.parse(content) as EventEntry[];
7375
} catch (error) {
7476
console.error('ERROR while loading events for change date picker', error);

source/lib/inline-menu-filter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export const DEFAULT_FILTER = '.+';
22

33
export function filterButtonText<T>(getCurrentFilterFunction: (ctx: T) => string | undefined): (ctx: T) => string {
44
return ctx => {
5-
let text = '🔎 Filter';
5+
let text = '🔎 Ab hier filtern';
66
const currentFilter = getCurrentFilterFunction(ctx);
77
if (currentFilter && currentFilter !== '.+') {
88
text += ': ' + currentFilter;

source/lib/types.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ export type Session = {
2323
adminuserquicklook?: number; // User ID
2424
adminuserquicklookfilter?: string;
2525
eventfilter?: string;
26-
generateChangeName?: string;
26+
eventPath?: string[]; // Path to the selected subdirectory
27+
eventDirectorySubDirectoryItems?: string[]; // Subdirectory item keys of the directory selected by eventPath
28+
generateChangeEventId?: EventId;
2729
generateChangeDate?: NaiveDateTime;
2830
generateChange?: Partial<Change>;
2931
page?: number;
@@ -37,7 +39,7 @@ export type Session = {
3739
export type Userconfig = {
3840
readonly admin?: true;
3941
calendarfileSuffix: string;
40-
events: Record<string, EventDetails>;
42+
events: Record<EventId, EventDetails>;
4143
mensa: MensaSettings;
4244
removedEvents?: RemovedEventsDisplayStyle;
4345
};
@@ -82,6 +84,15 @@ export type MensaSettings = MealWishes & {
8284
showAdditives?: boolean;
8385
};
8486

87+
export type EventId = `${number}_${number | string}`;
88+
89+
export type Events = Record<EventId, string>;
90+
91+
export type EventDirectory = {
92+
readonly subDirectories: Record<string, Partial<EventDirectory>>;
93+
readonly events: Events;
94+
};
95+
8596
export type EventEntry = {
8697
readonly name: string;
8798
readonly location: string;

source/menu/about.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const menu = new MenuTemplate<MyContext>(async ctx => {
1010

1111
const canteens = await getCanteenList();
1212
const canteenCount = canteens.length;
13-
const eventCount = await allEvents.count();
13+
const eventCount = allEvents.count();
1414

1515
const websiteLink = format.url(
1616
'hawhh.de/calendarbot/',

0 commit comments

Comments
 (0)