Improved layout, added warnings to result page

This commit is contained in:
Lukas Wölfer
2025-05-07 12:18:32 +02:00
parent b88a978606
commit 7cb4319a05
3 changed files with 45 additions and 14 deletions

23
main.ts
View File

@@ -1,7 +1,7 @@
import { Mwn, RecentChange } from 'npm:mwn' import { Mwn, RecentChange } from 'npm:mwn'
import process, { title } from "node:process"; import process, { title } from "node:process";
import { parseSummary } from "./parse.ts"; import { parseSummary } from "./parse.ts";
import { bucketEvents, writeSection } from "./write.ts"; import { bucketEvents, writeSections } from "./write.ts";
async function getWorkshopVideos(bot: Mwn): Promise<string[]> { async function getWorkshopVideos(bot: Mwn): Promise<string[]> {
const response = await bot.request({ const response = await bot.request({
@@ -29,13 +29,20 @@ export interface VideoDescription {
notes: string notes: string
path: string path: string
title: string title: string
nags: string[]
} }
async function fetchPages(pages: string[], bot: Mwn): Promise<VideoDescription[]> { async function fetchPages(pages: string[], bot: Mwn): Promise<VideoDescription[]> {
const d = await Promise.allSettled(pages.map(async (v) => const d = await Promise.allSettled(pages.map(async (v) =>
parseSummary(await new bot.Page(v).text(), v) parseSummary(await new bot.Page(v).text(), v)
)) ))
d.map((v, index) => ({ r: v, source: pages[index] })).filter((v): v is { source: string, r: PromiseRejectedResult } => v.r.status === "rejected").forEach(
v => console.warn(`Error parsing ${v.source}: ${v.r.reason}`
))
return d.filter(v => v.status === "fulfilled").map(v => v.value) return d.filter(v => v.status === "fulfilled").map(v => v.value)
} }
function sleep(ms: number): Promise<void> { function sleep(ms: number): Promise<void> {
@@ -45,6 +52,16 @@ function sleep(ms: number): Promise<void> {
async function watchdog(bot: Mwn, onChange: (paths: string[]) => Promise<void>) { async function watchdog(bot: Mwn, onChange: (paths: string[]) => Promise<void>) {
let last_change: string | undefined = undefined; let last_change: string | undefined = undefined;
let heartbeat_count = 0;
const heartbeat_count_max = 60;
const heartbeat = () => {
heartbeat_count += 1
if (heartbeat_count >= heartbeat_count_max) {
heartbeat_count = 0;
console.log(new Date(), "Heartbeat")
}
}
while (true) { while (true) {
const { last_change: l, new_file_changes } = await queryChanges(bot, last_change); const { last_change: l, new_file_changes } = await queryChanges(bot, last_change);
@@ -53,7 +70,7 @@ async function watchdog(bot: Mwn, onChange: (paths: string[]) => Promise<void>)
console.log(new_file_changes.length, "changes") console.log(new_file_changes.length, "changes")
await onChange(new_file_changes.map(v => v.title)) await onChange(new_file_changes.map(v => v.title))
} }
console.log(new Date(), "Heartbeat") heartbeat()
await sleep(30000); await sleep(30000);
} }
} }
@@ -94,7 +111,7 @@ async function main() {
await watchdog(bot, async (paths) => { await watchdog(bot, async (paths) => {
const relevantFiles = await getWorkshopVideos(bot) const relevantFiles = await getWorkshopVideos(bot)
const d = await fetchPages(relevantFiles, bot) const d = await fetchPages(relevantFiles, bot)
const t = writeSection(bucketEvents(d)) const t = writeSections(bucketEvents(d))
const response = await bot.save(title, "{{TOC|limit=3}}\n\n" + t, 'Triggered by changes to ' + paths.map(v => "[[" + v + "]]").join(", ")); const response = await bot.save(title, "{{TOC|limit=3}}\n\n" + t, 'Triggered by changes to ' + paths.map(v => "[[" + v + "]]").join(", "));
console.log(response) console.log(response)
}) })

View File

@@ -1,21 +1,22 @@
import { Mwn } from "mwn"; import { Mwn } from "mwn";
import { VideoDescription } from "./main.ts"; import { VideoDescription } from "./main.ts";
function parseTemplate(description: string): Record<string, string> { function parseTemplate(description: string): { warnings: string[] } & { [k: string]: string } {
const u = new (new Mwn().Wikitext)(description) const u = new (new Mwn().Wikitext)(description)
const r = u.parseTemplates({ const r = u.parseTemplates({
namePredicate: (name) => name === "DanceWorkshopDescription", namePredicate: (name) => name === "DanceWorkshopDescription",
}) })
const warnings = []
if (r.length < 1) { if (r.length < 1) {
throw new Error("Could not find DanceWorkshopDescription") throw new Error("Could not find DanceWorkshopDescription")
} }
if (r.length > 1) { if (r.length > 1) {
console.warn("More than one DanceWorkshopDescription template found") warnings.push("More than one DanceWorkshopDescription template found")
} }
const p = r[0] const p = r[0]
const properties = p.parameters.map(v => ({ [v.name]: v.value })) const properties = p.parameters.map(v => ({ [v.name]: v.value }))
return Object.assign({}, ...properties) return Object.assign({}, { warnings }, ...properties)
} }
function parseBlock(header: string, text: string): string { function parseBlock(header: string, text: string): string {
@@ -35,9 +36,10 @@ export function parseSummary(description: string, name: string): VideoDescriptio
const properties = parseTemplate(description) const properties = parseTemplate(description)
const b: Partial<VideoDescription> = {}; const b: Partial<VideoDescription> = {};
b.nags = properties.warnings
if (Object.hasOwn(properties, 'teachers')) { if (Object.hasOwn(properties, 'teachers')) {
console.warn(`Page ${name} has old template usage [contains 'teachers' key]`) b.nags.push(`Contains 'teachers' key instead of 'leader'/'follower'`)
const t = properties['teachers'].split('&').map(item => item.trim()); const t = properties['teachers'].split('&').map(item => item.trim());
b.teachers = t b.teachers = t
} else { } else {
@@ -52,15 +54,25 @@ export function parseSummary(description: string, name: string): VideoDescriptio
if (!Object.hasOwn(properties, 'patterns')) { if (!Object.hasOwn(properties, 'patterns')) {
console.warn(`Page ${name} has old template usage [no 'patterns' key]`) b.nags.push(`No 'patterns' key`)
b.patterns = parseBlock("Shown Patterns", description) try {
b.patterns = parseBlock("Shown Patterns", description)
} catch (e) {
b.nags.push("Shown Patterns: " + e)
b.patterns = ""
}
} else { } else {
b.patterns = properties['patterns'] b.patterns = properties['patterns']
} }
if (!Object.hasOwn(properties, 'notes')) { if (!Object.hasOwn(properties, 'notes')) {
console.warn(`Page ${name} has old template usage [no 'notes' key]`) b.nags.push(`No 'notes' key`)
b.notes = parseBlock("Notes", description) try {
b.notes = parseBlock("Notes", description)
} catch (e) {
b.nags.push("Notes: " + e)
b.notes = ""
}
} else { } else {
b.notes = properties['notes'] b.notes = properties['notes']
} }

View File

@@ -3,8 +3,9 @@ import { VideoDescription } from "./main.ts";
export function singleVideoDescription(video: VideoDescription): string { export function singleVideoDescription(video: VideoDescription): string {
const teachersList = video.teachers.map(v => "[[" + v + "]]").join(" & "); const teachersList = video.teachers.map(v => "[[" + v + "]]").join(" & ");
const nagElement = video.nags.length > 0 ? `<span title="${video.nags.join("&#010;")}">🔴</span>` : "";
return `=== ${video.title} === return `=== ${video.title} ===
Date:{{#time: Y-m-d (D) | ${video.date}}}<br> Date: {{#time: Y-m-d (D) | ${video.date}}} ${nagElement}<br>
Teachers: ${teachersList}<br> Teachers: ${teachersList}<br>
Level: ${video.level} Level: ${video.level}
@@ -21,13 +22,14 @@ ${video.notes}
<br clear=all>`; <br clear=all>`;
} }
export function writeSection(events: VideoDescription[][]): string { export function writeSections(events: VideoDescription[][]): string {
return events.map(v => { return events.map(v => {
const token = v[0] const token = v[0]
const event_name = token.event === token.location ? token.event : token.event + " " + token.location const event_name = token.event === token.location ? token.event : token.event + " " + token.location
// FIXME: This will break with videos in 75 years // FIXME: This will break with videos in 75 years
const event_date = event_name.match(/\d{2}|20\d{2}/) ? "" : new Date(token.date).getFullYear().toString() const event_contains_year = event_name.match(/\d{2}|20\d{2}/) === null
const event_date = event_contains_year ? "" : new Date(token.date).getFullYear().toString()
let r = `== ${event_name} ${event_date} ==\n` let r = `== ${event_name} ${event_date} ==\n`
r += `${v.length} Video${v.length <= 1 ? "" : "s"}\n` r += `${v.length} Video${v.length <= 1 ? "" : "s"}\n`