Google Apps Script Bot for GroupMe

Posted by:

|

On:

|

If you have an active GroupMe chat, you’ve probably experienced the chaos of important announcements
and event reminders getting lost in a never-ending stream of messages. This is where a bot comes in
handy! In this guide, we’ll look at how to set up a Google Apps Script bot that automatically posts:

  • Upcoming events from a Google Calendar
  • A weekly report and a mid-week weather outlook
  • Birthday reminders

All of these updates can go straight to your GroupMe group without you needing to do any extra work.

Why Do We Need a Bot?

GroupMe chats can get busy, making it easy for announcements and event details to get buried. By using
a bot, you can:

  • Gather important event information automatically from a Google Calendar.
  • Post it into the chat on a fixed schedule.
  • Keep everyone updated about birthdays, upcoming events, or local weather forecasts.

In other words, you set it up once, and the bot takes care of everything behind the scenes. No more
manually copying, pasting, or remembering to send updates!

Overview of the Script

Fetches calendar events for the next week (and also days 8–28 ahead).
Grabs the local weather forecast from
weather.gov.
Posts this information to a GroupMe group.

You’ll see places where the code references a personal GroupMe access token and a calendar ID. You will
need to input your info, which can be found at
the GroupMe Developer Site.

Full Script


/**************************************************************
  1) Configuration
 **************************************************************/
// -- Calendar
const CALENDAR_ID = "[email protected]";
const CALENDAR_LINK = "https://calendar.google.com/calendar/embed?src=YOUR_CALENDAR_ID&ctz=America%2FChicago";

// -- GroupMe Access
const GROUPME_ACCESS_TOKEN = "YOUR_GROUPME_ACCESS_TOKEN";

// -- Group IDs
const MAIN_GROUP_ID = "MAIN_CHAT_ID";                // For birthdays
const ANNOUNCEMENTS_GROUP_ID = "ANNOUNCEMENTS_CHAT_ID"; // For weekly updates

/**************************************************************
  2) Post Weekly Report
 **************************************************************/
function postWeeklyReport() {
  try {
    const now = new Date();
    const oneWeekLater = new Date(now.getTime() + 7  * 24 * 60 * 60 * 1000);
    const fourWeeksLater = new Date(now.getTime() + 28 * 24 * 60 * 60 * 1000);

    const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
    if (!calendar) {
      Logger.log("Could not find calendar with ID: " + CALENDAR_ID);
      return;
    }

    // --- Events in the next 7 days
    const next7DaysEvents = calendar.getEvents(now, oneWeekLater);
    let message = "";
    if (next7DaysEvents.length === 0) {
      message += "Weekly Report: No events scheduled in the next 7 days.\n";
    } else {
      message += "Weekly Report (Next 7 Days):\n";
      next7DaysEvents.forEach(event => {
        const title = event.getTitle();
        const start = event.getStartTime();
        const dateStr = Utilities.formatDate(
          start,
          Session.getScriptTimeZone(),
          "E MM/dd h:mm a"
        );
        message += `• ${dateStr}: ${title}\n`;
      });
    }

    // --- Events in days 8–28
    const futureEvents = calendar.getEvents(oneWeekLater, fourWeeksLater);
    if (futureEvents.length > 0) {
      message += "\n=== Events in the Future (8–28 days out) ===\n";
      futureEvents.forEach(event => {
        const title = event.getTitle();
        const start = event.getStartTime();
        const dateStr = Utilities.formatDate(
          start,
          Session.getScriptTimeZone(),
          "E MM/dd h:mm a"
        );
        message += `• ${dateStr}: ${title}\n`;
      });
    }

    // --- 7-Day Weather Outlook
    const weatherOutlook = getWeatherForecastHuntsvilleAL(7);
    message += `\n=== 7-Day Weather Outlook ===\n${weatherOutlook}\n`;

    // --- Calendar Link
    message += "\nFull Calendar:\n" + CALENDAR_LINK;

    // Post to the Announcements group
    postToGroupMeAnnouncements(message);

  } catch (err) {
    Logger.log("Error in postWeeklyReport(): " + err);
  }
}

/**************************************************************
  3) Mid-Week (Wednesday) 5-Day Outlook
 **************************************************************/
function postMidWeekOutlook() {
  try {
    const forecast5Day = getWeatherForecastHuntsvilleAL(5);
    let message = "Mid-Week Outlook (Next 5 Days):\n";
    message += forecast5Day + "\n\n";
    message += "Full Calendar:\n" + CALENDAR_LINK;

    postToGroupMeAnnouncements(message);

  } catch (err) {
    Logger.log("Error in postMidWeekOutlook(): " + err);
  }
}

/**************************************************************
  4) Birthday Messages
 **************************************************************/
function postBirthdayMessages() {
  try {
    // "Today" from midnight to midnight
    const startOfDay = new Date();
    startOfDay.setHours(0, 0, 0, 0);
    const endOfDay = new Date(startOfDay);
    endOfDay.setDate(endOfDay.getDate() + 1);

    const calendar = CalendarApp.getCalendarById(CALENDAR_ID);
    if (!calendar) {
      Logger.log("Could not find calendar with ID: " + CALENDAR_ID);
      return;
    }

    // For each event, look for "[Birthday]"
    const events = calendar.getEvents(startOfDay, endOfDay);
    events.forEach(event => {
      const title = event.getTitle();
      if (title.includes("[Birthday]")) {
        const namePart = title.replace("[Birthday]", "").trim();
        const msg = namePart 
          ? `Happy Birthday! ${namePart}`
          : "Happy Birthday!";
        postToGroupMeMainChat(msg);
      }
    });

  } catch (err) {
    Logger.log("Error in postBirthdayMessages(): " + err);
  }
}

/**************************************************************
  5) Post to GroupMe (Announcements)
 **************************************************************/
function postToGroupMeAnnouncements(message) {
  postChunkedToGroupMe(ANNOUNCEMENTS_GROUP_ID, message);
}

/**************************************************************
  6) Post to GroupMe (Main Chat)
 **************************************************************/
function postToGroupMeMainChat(message) {
  postChunkedToGroupMe(MAIN_GROUP_ID, message);
}

/**************************************************************
  7) postChunkedToGroupMe()
 **************************************************************/
function postChunkedToGroupMe(groupId, fullMessage) {
  // GroupMe enforces a ~1000 character limit. We use 900 as a safe cutoff.
  const MAX_LENGTH = 900;
  let startIndex = 0;
  
  while (startIndex < fullMessage.length) {
    const chunk = fullMessage.substring(startIndex, startIndex + MAX_LENGTH);
    startIndex += MAX_LENGTH;
    _sendMessageToGroup(groupId, chunk);
  }
}

/**************************************************************
  8) _sendMessageToGroup()
 **************************************************************/
function _sendMessageToGroup(groupId, text) {
  const url = "https://api.groupme.com/v3/groups/" + groupId
              + "/messages?token=" + GROUPME_ACCESS_TOKEN;
  
  const payload = {
    message: {
      text: text,
      source_guid: Utilities.getUuid()
    }
  };
  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(url, options);
}

/**************************************************************
  9) Weather Forecast Utility
 **************************************************************/
function getWeatherForecastHuntsvilleAL(numDays) {
  // Example lat/lon for Huntsville, AL
  const lat = 34.7304;
  const lon = -86.5861;
  const pointsUrl = `https://api.weather.gov/points/${lat},${lon}`;
  const headers = { "User-Agent": "GoogleAppsScript-Weather ([email protected])" };

  // 1) Get forecast URL
  let response = UrlFetchApp.fetch(pointsUrl, { 
    muteHttpExceptions: true,
    headers: headers 
  });
  const pointsData = JSON.parse(response.getContentText());
  if (!pointsData.properties || !pointsData.properties.forecast) {
    return "Weather forecast not available at this time.";
  }

  // 2) Fetch the actual forecast
  const forecastUrl = pointsData.properties.forecast;
  response = UrlFetchApp.fetch(forecastUrl, {
    muteHttpExceptions: true,
    headers: headers
  });
  const forecastData = JSON.parse(response.getContentText());
  const periods = forecastData.properties && forecastData.properties.periods 
                  ? forecastData.properties.periods 
                  : [];

  if (!periods.length) {
    return "No forecast data found.";
  }

  // 3) Build a short day/night summary
  let outlook = "";
  let dayCount = 0;

  for (let i = 0; i < periods.length && dayCount < numDays; i += 2) {
    const dayPeriod = periods[i];
    const nightPeriod = periods[i + 1];
    
    const date = new Date(dayPeriod.startTime);
    const dateStr = Utilities.formatDate(date, "America/Chicago", "EEEE M/d");

    const dayTemp  = dayPeriod.temperature;
    const dayPop   = dayPeriod.probabilityOfPrecipitation.value || 0;
    const dayDesc  = dayPeriod.shortForecast || "";

    let nightTemp = "N/A";
    let nightPop  = 0;
    let nightDesc = "";
    if (nightPeriod && nightPeriod.isDaytime === false) {
      nightTemp = nightPeriod.temperature;
      nightPop  = nightPeriod.probabilityOfPrecipitation.value || 0;
      nightDesc = nightPeriod.shortForecast || "";
    }

    outlook += `${dateStr}\n`;
    outlook += `High/Low: ${dayTemp}° / ${nightTemp}°\n`;
    outlook += `Day: ${dayDesc} (PoP: ${dayPop}%)\n`;
    outlook += `Night: ${nightDesc} (PoP: ${nightPop}%)\n\n`;
    dayCount++;
  }

  return outlook.trim();
}

/**************************************************************
  10) Optional: Create Time-Based Triggers
 **************************************************************/
function createTimeTriggers() {
  // Weekly Report (e.g., every Monday at 6 AM)
  ScriptApp.newTrigger("postWeeklyReport")
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.MONDAY)
    .atHour(6)
    .create();

  // Daily birthday check (6 AM)
  ScriptApp.newTrigger("postBirthdayMessages")
    .timeBased()
    .everyDays(1)
    .atHour(6)
    .create();

  // Mid-week (Wednesday) 5-day outlook
  ScriptApp.newTrigger("postMidWeekOutlook")
    .timeBased()
    .onWeekDay(ScriptApp.WeekDay.WEDNESDAY)
    .atHour(6)
    .create();

How It Works, Step by Step

Calendar Configuration

Define your calendar’s unique ID and a link to the calendar, so people can view it.

GroupMe Access

Provide your GroupMe access token and any relevant group IDs. You might have different IDs for the main
chat versus announcements.

Posting Logic

In functions like postWeeklyReport() and postMidWeekOutlook(), the script
grabs events and weather info, then builds a long text string describing everything. It then sends
this text to GroupMe.

Chunking

Because GroupMe has a 1,000-character limit, the script breaks long messages into 900-character
segments. Each chunk is posted in quick succession, ensuring your group receives the entire update.

Optional Triggers

You can schedule these functions to run automatically—such as every Monday morning or every day at
6 AM—by using Google Apps Script’s time-based triggers.

Getting Started

  • Create or Pick a Calendar: Make a new Google Calendar or use one you already have to store events.
  • Obtain the Calendar ID: In Google Calendar settings, under “Integrate Calendar,” you’ll see a calendar ID that looks somewhat like [email protected].
  • Obtain a GroupMe Access Token: If you haven’t already, check out the
    GroupMe Developer site
    for details on how to get an access token.
  • Plug IDs into the Script: Replace placeholder values (YOUR_CALENDAR_ID,
    YOUR_GROUPME_ACCESS_TOKEN, etc.) with your actual data.
  • Paste into Google Apps Script: Go to
    script.google.com
    (or within your Google Workspace), create a new project, and drop in this code.
  • Set Up Triggers: Either manually create time-based triggers from the Script Editor
    (under “Triggers”) or run the createTimeTriggers() function once.

Final Thoughts

A Google Apps Script bot that automatically posts event announcements and weather forecasts can save
your group from endless scrolling. You’ll have daily or weekly alerts, a quick view of upcoming
birthdays, and a handy weather outlook—right in your GroupMe chat. Feel free to adapt the code to
suit your group’s needs, whether that’s changing the date ranges, adding custom logic, or formatting
the messages differently.

Once you’ve mastered these basics—like fetching calendar data, interacting with the GroupMe API,
and chunking long messages—you can keep expanding your bot with new features. Until then, enjoy
the peace of mind that comes with never missing an important event again!