128 lines
3.6 KiB
TypeScript
128 lines
3.6 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<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();
|