# Cron Job: yt-sitrep-the-comms-channel

**Job ID:** 3235a7ce7fc4
**Run Time:** 2026-06-10 07:04:52
**Schedule:** 0 7 * * *

## Prompt

[IMPORTANT: The user has invoked the "youtube-channel-sitreps" skill, indicating they want you to follow its instructions. The full skill content is loaded below.]

---
name: youtube-channel-sitreps
description: "Create recurring YouTube channel sitreps and deliver them to Telegram."
platforms: [linux, macos, windows]
---

# YouTube Channel Sitreps

Use this skill when the user wants a recurring or one-off briefing based on one or more YouTube channels: latest upload, short summary, and a concluding assessment of why it matters in the channel's broader domain.

This is a **briefing workflow**, not a transcript workflow. Use the YouTube content skill for transcript extraction when you need video-level detail; use this skill when the goal is channel monitoring, editorial curation, or scheduled sitreps.

## Typical outputs

- Daily/weekly channel sitreps (compact format)
- **Nieuws24 format** (thematic groups with bold titles + short explanation per item, max 5 videos, concluding takeaway)
- Telegram-delivered briefings
- Multi-channel comparative briefings

## Core workflow

1. **Resolve the channel**
   - Prefer the canonical channel URL or handle.
   - **Verify the channel ID.** The user may provide a wrong UC ID. Use web_search to find the actual `@handle` and extract the correct `UC...` channel ID from the channel's about page or search snippets. Multiple channels can share similar names — verify subscriber count and content theme.
   - **About-page disambiguation.** A channel's `/about` page HTML contains multiple `channelId` and `browseId` values — these include the actual channel's ID plus IDs of *linked/referenced* channels (featured channels in the banner, external links). To find the real one: extract all browse IDs, then test each with the RSS feed at `https://www.youtube.com/feeds/videos.xml?channel_id=<ID>`. The feed's `<title>` element tells you which channel that ID belongs to — RSS doubles as a channel-discovery tool.
   - Use the channel's `/videos` page to identify the latest public upload.

2. **Extract the minimum useful facts**
   - Latest video title
   - Publish time / freshness (prefer exact ISO dates over relative "X days ago")
   - 1-sentence summary of the video
   - 2-3 recent channel highlights if relevant

   **Workflow priority (try in order):**

   A. **YouTube RSS feed** — `https://www.youtube.com/feeds/videos.xml?channel_id=<VALID_UC_ID>`. When the channel ID is correct, the RSS feed returns reliable data including exact ISO 8601 publish dates, view counts, star ratings, and full descriptions (including chapters). Parse with `grep -E "(<title>|<yt:videoId>|<published>|<media:statistics)"` for a quick overview. For full descriptions and structured data, use Python's `xml.etree.ElementTree`:

      ```python
      import xml.etree.ElementTree as ET
      ns = {'yt':'http://www.youtube.com/xml/schemas/2015','media':'http://search.yahoo.com/mrss/','atom':'http://www.w3.org/2005/Atom'}
      for e in root.findall('atom:entry', ns):
          title = e.find('atom:title', ns).text
          vid = e.find('yt:videoId', ns).text
          pub = e.find('atom:published', ns).text
          views = e.find('.//media:statistics', ns).attrib.get('views','')
          desc = (e.find('.//media:description', ns).text or '')[:600]
      ```

      This avoids encoding issues with grep on multi-line description content. The RSS feed is faster than any other method, avoids bot detection entirely, and gives exact dates (not relative timestamps). **RSS works for most channels with a valid UC ID.** If it returns 404, first re-verify the channel ID before concluding RSS is broken.

   B. **curl + CONSENT cookie bypass** — if RSS is confirmed unavailable for this channel, or if the RSS feed returns a different channel's content (e.g. a sub-channel or localized variant). See `references/youtube-sitrep-workflow.md` for the full curl command, JSON path map, and oEmbed recipe.

   C. **FALLBACK (browser)** — only when both RSS and curl fail. YouTube browser sessions loop back to the consent screen because the consent cookie doesn't persist across isolated Hermes browser sessions. Use `browser_console` with JS expression `document.querySelector('a[href*="/watch"]')?.href` to extract the first video URL without relying on the accessibility snapshot.

   **YouTube watch page metadata** — When you need exact ISO publish dates (not relative timestamps) after falling back to curl+CONSENT, extract the `uploadDate` from the watch page HTML's JSON-LD. This is more reliable than the relative timestamps in the videos grid:

   ```bash
   curl -sL -b "CONSENT=YES+US.us+V12" \
     -H "User-Agent: Mozilla/5.0 ..." \
     "https://www.youtube.com/watch?v=VIDEO_ID" \
     | grep -oP '"uploadDate":"[^"]*"'
   # → "uploadDate":"2026-06-05T00:09:17-07:00"
   ```

   The `CONSENT=YES+US.us+V12` cookie yields English timestamps; `YES+NL.nl+V12` yields Dutch. The watch page also contains `<meta name="description">` and the ytInitialPlayerResponse JSON blob for full video descriptions — prefer `grep -oP '"uploadDate":"[^"]*"'` for just the date, as it's the simplest extraction.

   **Video description from watch page** — The `<meta name="description">` tag is truncated at ~200 chars. For full descriptions (including chapter timestamps), extract from the watch page's ytInitialPlayerResponse JSON blob using the same brace-depth matching technique described in the reference workflow.

3. **Add the editorial conclusion**
   - End with a short conclusion about what the latest video means in the channel's wider niche or industry.
   - Keep the conclusion specific, not generic.

4. **Keep it compact**
   - The briefing should be readable in a phone notification.
   - Prefer bullets and short sentences.

## Telegram delivery pattern

When the user asks for scheduled delivery to Telegram:

- Use a cron job with a stable, class-level name.
- Default naming pattern: `yt-sitrep-<channel-slug>`.
- Deliver in the user's language unless they explicitly ask otherwise.
- If the user wants Belgium time, use `Europe/Brussels` and `0 9 * * *`.

## Email delivery pattern

When the user asks for email delivery alongside or instead of Telegram:

- Write the full sitrep to a local file first at a deterministic path (e.g. `/opt/data/cron/output/yt-<channel-slug>/YYYY-MM-DD.md`).

**Primary method — google_api.py (Hermes venv):** The Google Workspace API script lives at `/opt/data/skills/productivity/google-workspace/scripts/google_api.py` and uses the `googleapiclient` library installed in the Hermes venv. Run it with the Hermes venv's Python, not the system one:

  ```bash
  /opt/hermes/.venv/bin/python3 /opt/data/skills/productivity/google-workspace/scripts/google_api.py \
    gmail send \
    --to recipient@example.com \
    --subject "YT Sitrep - <Channel> - YYYY-MM-DD" \
    --body "$(cat /opt/data/cron/output/yt-<channel-slug>/YYYY-MM-DD.md)"
  ```

  **Python interpreter mismatch pitfall:** Running as `python3 google_api.py ...` (with `/usr/bin/python3`) crashes with `ModuleNotFoundError: No module named 'googleapiclient'`. The dependency lives only in the Hermes venv (`/opt/hermes/.venv/`). Always invoke via the Hermes venv's Python or the script's own path (which has the correct shebang).

  **CRLF pitfall:** The script may have CRLF line endings (`\r\n`) from Git checkout. If `execve` fails with "required file not found", run `sed -i 's/\r$//' /opt/data/skills/productivity/google-workspace/scripts/google_api.py` then retry.

**Fallback — stdlib-only Gmail send (no pip required):** When the Hermes venv is unavailable or pip is not installed, use the stdlib-only approach documented in `references/gmail-stdlib-fallback.md`. This uses only Python stdlib (`urllib`, `email.mime.text`, `base64`, `json`) with the existing OAuth refresh token at `/opt/data/google_token.json` to call the Gmail API directly. It refreshes expired tokens automatically.

  ```bash
  python3 << 'PYEOF'
  # Full script in references/gmail-stdlib-fallback.md
  # Adjust TOKEN_PATH, TO_ADDR, FROM_ADDR, SUBJECT, BODY_PATH as needed
  PYEOF
  ```

- Only skip email if the Gmail API itself fails (check the JSON response for `"id"` on success, or the HTTP response for errors). Still write the local file regardless.

## [SILENT] for cron jobs

When running as a cron job and no new videos exist since the last run, respond with exactly `[SILENT]` (nothing else) to suppress delivery. Never combine [SILENT] with content — either report findings normally or deliver [SILENT] alone.

## Related skills

- `youtube-content` for transcript extraction and video-to-text transformation.

## Reference material

See `references/youtube-sitrep-workflow.md` for the reusable briefing template, naming convention, and Telegram cron pattern.

The user has provided the following instruction alongside the skill invocation: [IMPORTANT: You are running as a scheduled cron job. DELIVERY: Your final response will be automatically delivered to the user — do NOT use send_message or try to deliver the output yourself. Just produce your report/output as your final response and the system handles the rest. SILENT: If there is genuinely nothing new to report, respond with exactly "[SILENT]" (nothing else) to suppress delivery. Never combine [SILENT] with content — either report your findings normally, or say [SILENT] and nothing more.]

Check The Comms Channel YouTube channel at https://www.youtube.com/@TheCommsChannel for new videos since the last run. Output in nieuws24 format: thematic groups with short title+explanation per item, up to 5 videos max, concluding takeaway. After creating the digest, write the full output to /opt/data/cron/output/yt-commschannel/YYYY-MM-DD.md (create dirs if needed). Then EMAIL the same content to tomstynen@gmail.com with subject "YT Sitrep - The Comms Channel - YYYY-MM-DD" using the GAPI script at /opt/data/skills/productivity/google-workspace/scripts/google_api.py. Only skip email if the Gmail API fails — still write the local file. If there are NO new videos since last run, write "No new videos" to the file and skip the email.

## Response

Email verzonden. Dit is de eerste sitrep — nieuwe video's sinds 11 mei.

---

## 📡 The Comms Channel — Sitrep 2026-06-10

**MeshCore QuickStart-reeks afgerond**
- **MeshCore Tips FAQ** (5 jun) — Finale van de QuickStart-serie: praktische tips, configuratievalkuilen, optimalisaties.
- **MeshCore Client Repeat Mode** (20 mei) — Client Repeat uitgelegd vs. Meshtastic, stap-voor-stap config.
- **MeshCore Repeater Flashing and Config** (14 mei) — SenseCAP P1 Pro, OTA bootloader, DFU flashing.

**Off-grid infrastructuur**
- **Vatrer 300ah Battery Setup** (19 mei) — 300Ah LiFePO4 Bluetooth battery als backup voor repeaterstation.

**Hardware**
- **New Blue WisMesh Tag** (11 mei) — Limited blue edition van de WisMesh Tag (IP66, card-form).

**Conclusie:** Het kanaal heeft de MeshCore QuickStart-serie afgerond en positioneert zich als dé MeshCore-bron. De focus verschuift van introductie naar operationeel netwerkbeheer — een teken dat het ecosysteem volwassen wordt.

✅ Lokaal geschreven → `/opt/data/cron/output/yt-commschannel/2026-06-10.md`
✅ Gemaild → tomstynen@gmail.com
