Files
dancing-summarizer/main.ts
2025-06-05 23:11:24 +02:00

139 lines
4.0 KiB
TypeScript

import { Mwn, RecentChange } from 'npm:mwn'
import process from "node:process";
import { parseSummary } from "./parse.ts";
import { bucketEvents, writeSections } from "./write.ts";
async function getWorkshopVideos(bot: Mwn): Promise<string[]> {
const response = await bot.request({
action: 'query',
list: 'allimages',
aiprefix: 'WCS ', // Search prefix
ailimit: 'max', // Maximum number of results
});
if (response.query === undefined) {
throw new Error("Did not receive response to file query")
}
// Extract and print the file titles
return response.query.allimages.map(function (image: { title: string }) { return image.title });
}
export interface VideoDescription {
teachers: string[]
location: string
event: string
level: string
date: string
patterns: string
notes: string
path: string
title: string
nags: string[]
}
async function fetchPages(pages: string[], bot: Mwn): Promise<{ pages: VideoDescription[], errors: string[] }> {
const d = await Promise.allSettled(pages.map(async (v) =>
parseSummary(await new bot.Page(v).text(), v)
))
const siteProblems: string[] = []
d.map((v, index) => ({ r: v, source: pages[index] }))
.filter((v): v is { source: string, r: PromiseRejectedResult } => v.r.status === "rejected")
.forEach(
v => {
const e = `Error parsing ${v.source}: ${v.r.reason}`
console.warn(e)
siteProblems.push(e)
}
)
return { pages: d.filter(v => v.status === "fulfilled").map(v => v.value), errors: siteProblems }
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function watchdog(bot: Mwn, onChange: (paths: string[]) => Promise<void>) {
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) {
const { last_change: l, new_file_changes } = await queryChanges(bot, last_change);
last_change = l
if (new_file_changes.length > 0) {
console.log(new_file_changes.length, "changes")
await onChange(new_file_changes.map(v => v.title))
}
heartbeat()
await sleep(30000);
}
}
async function queryChanges(bot: Mwn, last_change: string | undefined): Promise<{ last_change: string | undefined; new_file_changes: RecentChange[]; }> {
const d = await bot.request({
action: "query",
list: "recentchanges",
format: "json",
formatversion: 2,
rcprop: "title|timestamp|ids|user",
rctoponly: 1,
rcdir: 'older',
rcend: last_change || "2000-01-01T12:00:00Z",
});
const changes: RecentChange[] = d.query?.recentchanges;
const new_file_changes = changes
.filter(v => v.title.startsWith("File:WCS "))
.filter(v => last_change === undefined || new Date(v.timestamp) > new Date(last_change));
return { last_change: changes[0].timestamp, new_file_changes };
}
async function main() {
const bot = new Mwn({
apiUrl: process.env.URL || 'https://dancing.thasky.one/api.php',
username: process.env.BOTNAME,
password: process.env.BOTPW,
userAgent: 'mwn bot',
});
const title = 'Video Descriptions (Automated)'
try {
await bot.login();
await watchdog(bot, async (paths) => {
const relevantFiles = await getWorkshopVideos(bot)
const { pages: d, errors: parseErrors } = await fetchPages(relevantFiles, bot)
const t = writeSections(bucketEvents(d))
const trigger_summary = 'Triggered by changes to ' + paths.map(v => `[[${v}]]`).join(", ")
const error_summary = parseErrors.join("\n")
const summary = [trigger_summary, error_summary].filter(v => v !== undefined && v.length > 0).join("\n")
const response = await bot.save(title, "{{TOC|limit=3}}\n\n" + t, summary);
console.log(response)
})
} catch (error) {
console.error('Error:', error);
} finally {
await bot.logout();
}
}
main();