Sync YouTube metadata #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Sync YouTube metadata | |
| # Watches the LIVE YouTube Data API v3 for drift between the hand-maintained video | |
| # catalog (src/data/videos.ts) and YouTube, and opens/updates ONE tracking issue when | |
| # something needs attention: blank duration/publishedAt to fill, a title you renamed on | |
| # YouTube, a video the API no longer returns (deleted/private → dead embed), or a channel | |
| # upload not yet in the catalog. It NEVER edits the catalog and NEVER pushes to main — | |
| # you read the issue and edit videos.ts by hand. | |
| # | |
| # Trigger: weekly cron + manual. (YouTube is external to this repo, so a schedule is the | |
| # right shape — `push` would only re-check after unrelated commits. Run "Run workflow" | |
| # anytime.) Needs a YOUTUBE_API_KEY repo secret (read-only key, restricted to YouTube | |
| # Data API v3). Quota per run ~5 units of the 10,000/day default. | |
| # | |
| # Injection-safety: the `run:` step contains no `${{ }}` expansion; the github-script step | |
| # reads every value from `env:` via process.env (never interpolated into a shell), and all | |
| # of those values come from THIS repo's own script — no issue/PR/commit text is consumed. | |
| on: | |
| schedule: | |
| - cron: '0 12 * * 1' # every Monday 12:00 UTC (an hour off the DataWeave check so they don't collide) | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| issues: write | |
| concurrency: | |
| group: sync-youtube-metadata | |
| cancel-in-progress: false | |
| jobs: | |
| sync: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Set up Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Check catalog vs. YouTube Data API | |
| id: sync | |
| env: | |
| YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }} # passed via env, never on the command line | |
| run: node scripts/sync-youtube-metadata.mjs --discover | |
| - name: Open / update tracking issue | |
| if: steps.sync.outputs.drift == 'true' | |
| uses: actions/github-script@v7 | |
| env: | |
| ISSUE_TITLE: ${{ steps.sync.outputs.issue_title }} | |
| ISSUE_BODY: ${{ steps.sync.outputs.issue_body }} | |
| FILL_COUNT: ${{ steps.sync.outputs.fill_count }} | |
| DRIFT_COUNT: ${{ steps.sync.outputs.drift_count }} | |
| with: | |
| script: | | |
| const { ISSUE_TITLE, ISSUE_BODY, FILL_COUNT, DRIFT_COUNT } = process.env; | |
| const label = 'youtube-sync'; | |
| // Ensure the label exists (ignore "already exists"). | |
| try { | |
| await github.rest.issues.createLabel({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| name: label, color: 'c4302b', | |
| description: 'Video catalog out of sync with the YouTube Data API', | |
| }); | |
| } catch (e) { if (e.status !== 422) throw e; } | |
| // Reuse the existing open tracking issue if there is one (don't spam a new | |
| // issue every week — update the body and ping instead). | |
| const existing = await github.paginate(github.rest.issues.listForRepo, { | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| state: 'open', labels: label, per_page: 100, | |
| }); | |
| if (existing.length) { | |
| const issue = existing[0]; | |
| await github.rest.issues.update({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| issue_number: issue.number, title: ISSUE_TITLE, body: ISSUE_BODY, | |
| }); | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| issue_number: issue.number, | |
| body: `🔁 Re-checked: ${FILL_COUNT} field(s) to fill, ${DRIFT_COUNT} to verify. Details updated above.`, | |
| }); | |
| core.info(`Updated existing issue #${issue.number}`); | |
| } else { | |
| const created = await github.rest.issues.create({ | |
| owner: context.repo.owner, repo: context.repo.repo, | |
| title: ISSUE_TITLE, body: ISSUE_BODY, labels: [label], | |
| }); | |
| core.info(`Opened issue #${created.data.number}`); | |
| } |