CK Videos Checklist

Checking connection…
★★★ ready · 📅 scheduled · ★★ prepped · shot unprepped · duplicates · 💡 idea · total
🕐 Version Historyexpand
No versions yet.
🔗 Google Sheets Syncexpand
Not connected.
📋 How to connect Google Sheetsexpand

Step 1. Create a new Google Sheet.

Step 2. Extensions → Apps Script → replace all code with the script below → click Save.

▸ Show code
/*** CK VIDEOS CHECKLIST — Apps Script (Sheets + Calendar) ***/
/*** VERSION: 2026-08-14a — adds ping (source identity) + save-token verify ***/

function doGet(e) {
  const p = e && e.parameter;
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Data') || ss.insertSheet('Data');

  if (p && p.action === 'load') {
    return ContentService.createTextOutput(
      JSON.stringify({
        data: sheet.getRange('A1').getValue(),
        teamNotes: sheet.getRange('C1').getValue()
      }))
      .setMimeType(ContentService.MimeType.JSON);
  }

  // Identity check — lets the dashboard PROVE which Spreadsheet + deployment
  // it is actually talking to, and show it visibly on screen.
  if (p && p.action === 'ping') {
    return ContentService.createTextOutput(
      JSON.stringify({
        status: 'ok',
        spreadsheetId: ss.getId(),
        spreadsheetName: ss.getName(),
        spreadsheetUrl: ss.getUrl(),
        scriptId: ScriptApp.getScriptId(),
        lastSaveTs: sheet.getRange('B1').getValue(),
        serverTime: new Date().toISOString()
      }))
      .setMimeType(ContentService.MimeType.JSON);
  }

  // Double-check protocol — after a save, the dashboard polls this with the
  // exact token it just sent. A match PROVES that specific save round-tripped
  // to THIS spreadsheet (not just that "some" save happened somewhere).
  if (p && p.action === 'verify') {
    const storedToken = sheet.getRange('D1').getValue();
    return ContentService.createTextOutput(
      JSON.stringify({
        status: 'ok',
        supported: true,
        match: !!(p.token && storedToken && String(storedToken) === String(p.token)),
        storedToken: storedToken,
        spreadsheetId: ss.getId(),
        spreadsheetName: ss.getName()
      }))
      .setMimeType(ContentService.MimeType.JSON);
  }

  return ContentService.createTextOutput(JSON.stringify({ status: 'ok' }))
    .setMimeType(ContentService.MimeType.JSON);
}

function doPost(e) {
  const body = JSON.parse(e.postData.contents);
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName('Data') || ss.insertSheet('Data');

  if (body.action === 'save') {
    const ex = sheet.getRange('A1').getValue();
    const exTs = sheet.getRange('B1').getValue();
    if (ex) {
      const lr = sheet.getLastRow();
      sheet.getRange('A' + (lr + 1)).setValue(ex);
      sheet.getRange('B' + (lr + 1)).setValue(exTs);
    }
    sheet.getRange('A1').setValue(body.data);
    sheet.getRange('B1').setValue(new Date().toISOString());
    if (body.teamNotes !== undefined) sheet.getRange('C1').setValue(body.teamNotes);
    // Store the per-save token so ?action=verify can prove this exact save landed.
    if (body.saveToken !== undefined) sheet.getRange('D1').setValue(body.saveToken);
    if (body.humanRows && body.humanRows.length) {
      let vs = ss.getSheetByName('Videos') || ss.insertSheet('Videos');
      vs.clearContents();
      vs.getRange(1, 1, body.humanRows.length, body.humanRows[0].length).setValues(body.humanRows);
      vs.getRange(1, 1, 1, body.humanRows[0].length).setFontWeight('bold');
      vs.setFrozenRows(1);
    }
  }

  if (body.action === 'calendar') {
    const result = upsertCalendarEvent(body);
    return ContentService.createTextOutput(JSON.stringify({ status: 'ok', cal: result }))
      .setMimeType(ContentService.MimeType.JSON);
  }

  if (body.action === 'calendar_delete') {
    const result = deleteCalendarEvent(body.itemId, body.tag);
    return ContentService.createTextOutput(JSON.stringify({ status: 'ok', cal: result }))
      .setMimeType(ContentService.MimeType.JSON);
  }

  return ContentService.createTextOutput(JSON.stringify({ status: 'ok' }))
    .setMimeType(ContentService.MimeType.JSON);
}

function upsertCalendarEvent(body) {
  if (!body.itemId) return 'no-id';
  const cal = CalendarApp.getDefaultCalendar();

  const tag = body.tag || ('ExcelSpine_' + (body.start || '').split('T')[0]);
  const legacyTag = '[CKVID:' + body.itemId + ']';

  const start = new Date(body.start);
  const end = new Date(start.getTime() + 60 * 60 * 1000);
  const title = body.title || 'CK Video';

  const purpose = body.purpose || '';
  const notes = body.notes || '';
  const visibleDesc = [purpose, notes].filter(Boolean).join('\n\n');
  const desc = (visibleDesc ? visibleDesc + '\n\n' : '') + tag;

  const searchFrom = new Date(start.getTime() - 365 * 24 * 60 * 60 * 1000);
  const searchTo   = new Date(start.getTime() + 365 * 24 * 60 * 60 * 1000);
  const events = cal.getEvents(searchFrom, searchTo);

  let deleted = 0;
  for (let i = 0; i < events.length; i++) {
    const d = events[i].getDescription() || '';
    if (d.indexOf(tag) !== -1 || d.indexOf(legacyTag) !== -1) {
      events[i].deleteEvent();
      deleted++;
    }
  }

  cal.createEvent(title, start, end, { description: desc });
  return deleted > 0 ? 'replaced' : 'created';
}

function deleteCalendarEvent(itemId, tag) {
  const cal = CalendarApp.getDefaultCalendar();
  const legacyTag = itemId ? '[CKVID:' + itemId + ']' : null;
  const now = new Date();
  const searchFrom = new Date(now.getTime() - 2 * 365 * 24 * 60 * 60 * 1000);
  const searchTo   = new Date(now.getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
  const events = cal.getEvents(searchFrom, searchTo);

  let deleted = 0;
  for (let i = 0; i < events.length; i++) {
    const d = events[i].getDescription() || '';
    const matchNew = tag && d.indexOf(tag) !== -1;
    const matchLegacy = legacyTag && d.indexOf(legacyTag) !== -1;
    if (matchNew || matchLegacy) {
      events[i].deleteEvent();
      deleted++;
    }
  }
  return deleted > 0 ? 'deleted-' + deleted : 'not-found';
}

/*** ONE-TIME CLEANUP — run manually from Apps Script editor ***/
function cleanupCountOnly() { return _cleanup(false); }
function cleanupDeleteNow() { return _cleanup(true); }
function _cleanup(reallyDelete) {
  const cal = CalendarApp.getDefaultCalendar();
  const now = new Date();
  const from = new Date(now.getTime() - 2 * 365 * 24 * 60 * 60 * 1000);
  const to   = new Date(now.getTime() + 2 * 365 * 24 * 60 * 60 * 1000);
  const events = cal.getEvents(from, to);
  let hit = 0;
  for (let i = 0; i < events.length; i++) {
    const d = events[i].getDescription() || '';
    const t = events[i].getTitle() || '';
    if (d.indexOf('[CKVID:') !== -1 || d.indexOf('[CK Video]') !== -1
        || d.indexOf('ExcelSpine_') !== -1 || t.indexOf('[CK Video]') !== -1) {
      hit++;
      if (reallyDelete) events[i].deleteEvent();
    }
  }
  const msg = (reallyDelete ? 'DELETED ' : 'WOULD DELETE ') + hit + ' events.';
  Logger.log(msg);
  return msg;
}

/*** ONE-TIME DIAGNOSTIC — run manually to confirm which file this script is bound to ***/
function whichSpreadsheetAmIBoundTo() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  Logger.log('Name: ' + ss.getName());
  Logger.log('ID: ' + ss.getId());
  Logger.log('URL: ' + ss.getUrl());
  Logger.log('Script ID: ' + ScriptApp.getScriptId());
  return ss.getName() + ' | ' + ss.getId();
}

Step 3. Deploy → New Deployment → Web App → Anyone → Copy URL.

Step 4. Paste URL above and save.

⚠ Redeploying later? Use Deploy → Manage Deployments → ✎ Edit → New Version — NOT "New Deployment". A brand-new deployment gets a brand-new URL and silently breaks every browser that still has the old one cached.

Schedule Video

6:00 AM
8:00 AM
9:00 AM
10:00 AM
12:00 PM
2:00 PM
4:00 PM
6:00 PM
Excel Spine Dashboards