diff --git a/.bauke/bin/edit-youtube-video b/.bauke/bin/edit-youtube-video new file mode 100755 index 0000000..a1970b3 --- /dev/null +++ b/.bauke/bin/edit-youtube-video @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +deno run \ + --allow-read \ + --allow-run="youtube3" \ + "$BAUKE_DIR/scripts/edit-youtube-video.ts" \ + "$@" diff --git a/.bauke/scripts/edit-youtube-video.ts b/.bauke/scripts/edit-youtube-video.ts new file mode 100644 index 0000000..c6fda37 --- /dev/null +++ b/.bauke/scripts/edit-youtube-video.ts @@ -0,0 +1,86 @@ +import { Command } from "https://deno.land/x/cliffy@v0.25.5/command/mod.ts"; +import { tomlFrontmatter } from "./utilities.ts"; + +type Frontmatter = { + page_title: string; + id: string; + speedrun?: { + entry: string; + leaderboard: string; + chapters?: Array<[string, string]>; + mods?: string[]; + }; +}; + +async function main(): Promise { + const { options } = await new Command() + .name("edit-youtube-video") + .description("Edit a YouTube video title and description") + .option( + "--category-id ", + "The categoryId of the video.", + { default: 20 }, + ) + .option("--description ", "The new description of the video.") + .option("--dry-run", "Do everything apart from calling the YouTube API.") + .option("--id ", "The ID of the video.") + .option( + "--markdown-file ", + "A Markdown file with TOML frontmatter.", + { + required: true, + }, + ) + .option("--title ", "The new title of the video.") + .parse(Deno.args); + + const frontmatter = tomlFrontmatter<Frontmatter>( + await Deno.readTextFile(options.markdownFile), + )[0]; + + const categoryId = options.categoryId; + const description = formatDescription(frontmatter); + const title = options.title ?? frontmatter.page_title; + const videoId = options.id ?? frontmatter.id; + + console.log({ categoryId, title, videoId }); + console.log(description); + if (options.dryRun) { + return; + } + + await Deno.run({ + cmd: [ + "youtube3", + "videos", + "update", + "-r", + `id=${videoId}`, + "-r", + `snippet.category-id=${categoryId}`, + "-r", + `snippet.description=${description}`, + "-r", + `snippet.title=${title}`, + ], + }).status(); +} + +function formatDescription(frontmatter: Frontmatter): string { + let description = "See all details on my website\n"; + description += `https://bauke.xyz/v/${frontmatter.id}/\n`; + + const chapters = frontmatter.speedrun?.chapters ?? []; + if (chapters.length > 0) { + description += `\nChapters\n`; + for (const [timestamp, text] of chapters) { + description += `${timestamp} ${text}\n`; + } + } + + return description; +} + +if (import.meta.main) { + void main(); +} diff --git a/.bauke/scripts/utilities.ts b/.bauke/scripts/utilities.ts index db2cd79..7813e2f 100644 --- a/.bauke/scripts/utilities.ts +++ b/.bauke/scripts/utilities.ts @@ -1,3 +1,4 @@ +import { parse } from "https://deno.land/std@0.167.0/encoding/toml.ts"; import { TextDecoder } from "https://deno.land/std@0.167.0/node/util.ts"; export async function runAndReturnStdout( @@ -6,3 +7,28 @@ export async function runAndReturnStdout( const process = Deno.run({ stdout: "piped", ...options }); return new TextDecoder().decode(await process.output()); } + +export function tomlFrontmatter<T>( + data: string, +): [T, string] { + const startMarker = "---toml\n"; + const endMarker = "\n---\n"; + + let start = data.indexOf(startMarker); + let end = data.indexOf(endMarker); + + if (start === -1 || end === -1) { + throw new Error("Missing frontmatter"); + } + + if (start !== 0) { + throw new Error("Frontmatter not at beginning of data"); + } + + start += startMarker.length; + const frontmatter = data.slice(start, end); + + end += endMarker.length; + const extra = data.slice(end); + return [parse(frontmatter) as T, extra.trimStart()]; +}