Files
dancing-summarizer/main.ts
2025-05-07 12:18:32 +02:00

128 lines
3.6 KiB
TypeScript

import { Mwn, RecentChange } from 'npm:mwn'
import process, { title } 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<VideoDescription[]> {
const d = await Promise.allSettled(pages.map(async (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)
}
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 d = await fetchPages(relevantFiles, bot)
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(", "));
console.log(response)
})
} catch (error) {
console.error('Error:', error);
} finally {
await bot.logout();
}
}
main();