ggbro Plugins

Session Coach

com.ggbro.plugin.session-coach · v3.0.0 · ui.toast, ui.panel, ui.sound, storage, app.context

Context-aware wellness coach — custom reminders, tip progress, and adjustable check-ins.

Sandbox example — full pack source. Everything below runs in the plugin iframe via window.ggbro. This is the complete plugin.

manifest.json

Raw
{
  "id": "com.ggbro.plugin.session-coach",
  "name": "Session Coach",
  "version": "3.0.0",
  "author": "ggbro",
  "description": "Context-aware wellness coach — custom reminders, tip progress, and adjustable check-ins.",
  "main": "plugin.js",
  "styles": "styles.css",
  "permissions": ["ui.toast", "ui.panel", "ui.sound", "storage", "app.context"]
}

plugin.js

Raw
/**
 * Session Coach v3 — context-aware tips, custom notes, progress.
 */
(async function () {
  var started = Date.now();
  var tipsShown = 0;
  var autoCheckins = true;
  var intervalMin = 60;
  var checkTimer = null;
  var lastTip = '';
  var customNote = '';
  var ctxLabel = '—';
  var tips = [
    'Tip: pin busy servers and mute noisy channels for a cleaner sidebar.',
    'Tip: Streamer Mode hides personal info when you’re live.',
    'Tip: Use search to hop channels fast.',
    'Tip: Favorite GIFs live in the GIF picker for one-click reactions.',
    'Tip: YouTube Playlists can share a room queue with friends.',
    'Tip: Plugins stay sandboxed — only install packs you trust.',
    'Tip: Take a sip of water. Hydration is a power move.',
    'Tip: Stretch your shoulders for 20 seconds — future you says thanks.',
    'Tip: The 20-20-20 rule — every 20 min, look 20 ft away for 20 sec.',
  ];

  try {
    tipsShown = parseInt((await ggbro.storage.get('tipsShown')).value || '0', 10) || 0;
    autoCheckins = ((await ggbro.storage.get('auto')).value || '1') !== '0';
    var iv = parseInt((await ggbro.storage.get('intervalMin')).value || '60', 10);
    if (iv >= 15 && iv <= 180) intervalMin = iv;
    customNote = (await ggbro.storage.get('customNote')).value || '';
    lastTip = (await ggbro.storage.get('lastTip')).value || '';
  } catch (e) {
    ggbro.log('storage', e && e.message);
  }

  async function refreshContext() {
    try {
      var ctx = await ggbro.getContext();
      if (!ctx || !ctx.view) {
        ctxLabel = 'unknown';
        return;
      }
      if (ctx.view === 'channel') ctxLabel = 'In a channel' + (ctx.serverId ? ' · server' : '');
      else if (ctx.view === 'dm') ctxLabel = 'In a DM';
      else if (ctx.view === 'group-dm') ctxLabel = 'In a group DM';
      else if (ctx.view === 'friends') ctxLabel = 'Friends / home';
      else ctxLabel = String(ctx.view);
    } catch (e) {
      ctxLabel = 'context off';
    }
  }

  function pickTip() {
    return tips[Math.floor(Math.random() * tips.length)];
  }

  function formatUptime(ms) {
    var mins = Math.floor(ms / 60000);
    if (mins < 60) return mins + ' min';
    var hrs = Math.floor(mins / 60);
    var rem = mins % 60;
    return hrs + 'h ' + rem + 'm';
  }

  async function paint() {
    await refreshContext();
    await ggbro.ui.registerPanel({
      id: 'coach',
      title: 'Session Coach',
      schema: [
        {
          type: 'notice',
          id: 'where',
          text: 'You’re here: ' + ctxLabel + ' · session ' + formatUptime(Date.now() - started),
          tone: 'info',
        },
        { type: 'section', id: 's0', title: 'Session' },
        { type: 'text', id: 'up', label: 'Time in app', value: formatUptime(Date.now() - started) },
        { type: 'progress', id: 'tipsBar', label: 'Tips shown (session goals)', value: Math.min(tipsShown, 12), max: 12 },
        { type: 'text', id: 'shown', label: 'Tips shown', value: String(tipsShown) },
        { type: 'text', id: 'last', label: 'Last tip', value: lastTip || '(none yet)' },
        { type: 'section', id: 's1', title: 'Coach' },
        {
          type: 'textarea',
          id: 'note',
          label: 'Personal reminder (optional)',
          value: customNote,
          placeholder: 'e.g. Stand up every hour…',
          rows: 2,
        },
        { type: 'button', id: 'tip', label: 'Give me a tip' },
        { type: 'button', id: 'checkin', label: 'Check in now' },
        { type: 'toggle', id: 'auto', label: 'Automatic check-ins', value: autoCheckins },
        {
          type: 'slider',
          id: 'interval',
          label: 'Check-in interval (minutes)',
          value: intervalMin,
          min: 15,
          max: 180,
          step: 15,
        },
      ],
    });
  }

  function bumpTip(msg) {
    tipsShown += 1;
    lastTip = String(msg || '').slice(0, 200);
    ggbro.storage.set('tipsShown', String(tipsShown)).catch(function () {});
    ggbro.storage.set('lastTip', lastTip).catch(function () {});
  }

  function showTip(extra) {
    var body = pickTip();
    if (customNote) body = customNote + ' · ' + body;
    var msg = (extra ? extra + ' ' : '') + body;
    bumpTip(msg);
    ggbro.playSound({ id: 'friendRequest' }).catch(function () {});
    ggbro.toast({ message: msg.slice(0, 200), type: 'info' });
    paint().catch(function () {});
  }

  function armAuto() {
    if (checkTimer) clearInterval(checkTimer);
    checkTimer = null;
    if (!autoCheckins) return;
    checkTimer = setInterval(function () {
      showTip('Session check-in — you’ve been here ' + formatUptime(Date.now() - started) + '.');
    }, intervalMin * 60 * 1000);
  }

  await paint();
  showTip();
  armAuto();

  setInterval(function () {
    paint().catch(function () {});
  }, 60000);

  ggbro.onEvent(function (event, payload) {
    if (event === 'context') {
      paint().catch(function () {});
      return;
    }
    if (event !== 'panelAction' || !payload) return;
    if (payload.fieldId === 'tip') {
      showTip();
      return;
    }
    if (payload.fieldId === 'checkin') {
      showTip('Manual check-in — uptime ' + formatUptime(Date.now() - started) + '.');
      return;
    }
    if (payload.fieldId === 'note') {
      customNote = String(payload.value || '').slice(0, 200);
      ggbro.storage.set('customNote', customNote).catch(function () {});
      paint();
      ggbro.toast({ message: customNote ? 'Reminder saved' : 'Reminder cleared', type: 'success' });
      return;
    }
    if (payload.fieldId === 'auto') {
      autoCheckins = !!payload.value;
      ggbro.storage.set('auto', autoCheckins ? '1' : '0').catch(function () {});
      armAuto();
      ggbro.toast({
        message: autoCheckins ? 'Auto check-ins on' : 'Auto check-ins off',
        type: 'info',
      });
      return;
    }
    if (payload.fieldId === 'interval') {
      intervalMin = Math.max(15, Math.min(180, Number(payload.value) || 60));
      ggbro.storage.set('intervalMin', String(intervalMin)).catch(function () {});
      armAuto();
      paint();
      ggbro.toast({ message: 'Interval set to ' + intervalMin + ' min', type: 'info' });
    }
  });
})();

styles.css

Raw
:root {
  --ggbro-plugin-session-coach: 1;
}

README.md

Raw
# Session Coach (v2)

Wellness panel: tip button, check-in now, auto check-in toggle, and interval select (30 / 60 / 120 min).