scripts/deno/iframe-titles.ts (172 lines of code) (raw):

import { array, object, string } from 'npm:zod@3'; import { DOMParser, Element, Node, } from 'https://deno.land/x/deno_dom@v0.1.22-alpha/deno-dom-wasm.ts'; import { octokit } from './github.ts'; const searchParams = new URLSearchParams({ 'api-key': Deno.env.get('CAPI_KEY') ?? 'test', 'query-fields': 'body', q: 'iframe', 'show-fields': 'body', 'page-size': String(100), }); const url = new URL( `search?${searchParams}`, 'http://content.guardianapis.com/', ); const schema = object({ response: object({ status: string(), results: array( object({ id: string(), webTitle: string(), fields: object({ body: string(), }), }), ), }), }); const { response: { results }, } = await fetch(url.toString()) .then((r) => r.json()) .then(schema.parse); const parser = new DOMParser(); const isElement = (n: Node): n is Element => n instanceof Element; type Status = 'ok' | 'warning' | 'missing'; type Info = { status: Status; attr: string; value: string; }; type Ok = Info & { status: 'ok' }; type Warning = Info & { status: 'warning' }; type Missing = Info & { status: 'missing' }; type Article = { url: string; title: string; iframes: Array<Ok | Warning | Missing>; }; const getStatus = (iframes: Article['iframes']): Status => { if (iframes.length === 0) return 'missing'; if (iframes.some(({ status }) => status === 'missing')) return 'missing'; if (iframes.some(({ status }) => status === 'warning')) return 'warning'; return 'ok'; }; const promises = results.map(async ({ id, webTitle: title }) => { const url = `https://www.theguardian.com/${id}`; const body = await fetch(url).then((r) => r.text()); const document = parser.parseFromString(body, 'text/html'); const attrs = ['title', 'srcDoc', 'src'] as const; const iframes: Article['iframes'] = [ ...(document?.querySelectorAll('iframe') ?? []), ] .filter(isElement) .map((iframe) => { const attr = attrs.find((attr) => iframe.getAttribute(attr)); if (!attr) throw new Error('could not get any attribute'); const value = iframe.getAttribute(attr) ?? ''; switch (attr) { case 'title': { // Check if the title contains less than 9 different letters const warning = attr === 'title' && new Set(value?.split('')).size < 9; return { status: warning ? 'warning' : 'ok', attr, value, }; } case 'srcDoc': { return { status: 'missing', attr, // `srcdoc` are too long for github issues! value: value .replace(/<head>.+<\/head>/i, '') .slice(0, 420), }; } case 'src': return { status: 'missing', attr, value, }; default: return { status: 'missing', attr: '???', value: '???', }; } }); const result = { url, title, status: getStatus(iframes), iframes, }; return result; }); const articles = await Promise.all(promises); const missing = articles.filter(({ status }) => status === 'missing'); const warning = articles.filter(({ status }) => status === 'warning'); const ok = articles.filter(({ status }) => status === 'ok'); const statusEmoji = { missing: '🚫', warning: '⚠️', ok: '✅', } as const; const formatter = (articles: Article[], checked: boolean): string => articles .flatMap(({ url, title, iframes }) => { const frames = iframes.length ? iframes.map( ({ attr, value, status }) => ` - ${statusEmoji[status]} \`${attr}\`: \`${value}\``, ) : [ ` - ${ statusEmoji['missing'] } All iframes are renderd Client-side`, ]; return [`- [${checked ? 'X' : ' '}] [${title}](${url})`, ...frames]; }) .join('\n'); const body = ` # List of iframes with missing titles > **Note** > Do not attempt to fix these individually. This is a list to track recent issues > This list is generated [from CAPI](${url}) ## Missing ${formatter(missing, false)} ## Warning ${formatter(warning, true)} ## Ok ${formatter(ok, true)} `; const issue_number = 5510; if (!octokit) { console.log(body); Deno.exit(); } try { const { data: { html_url }, } = await octokit.rest.issues.update({ owner: 'guardian', repo: 'dotcom-rendering', issue_number, body, }); console.info(`Updated list of issues for dotcom-rendering#${issue_number}`); console.info(html_url); } catch (error) { // do_something console.warn(`Failed to update issue #${issue_number}`); console.error(error); console.log(body); } Deno.exit();