DEV Community

kurab
kurab

Posted on

Serverless chat bot by Google Apps Script - step/step

TL;DR

By using "GAS(Google Apps Script)", notify today's events on "Google Calendar" to "Google Chat", "Chatwork" and "LINE Messenger" every morning.

All you need is

  • Web browser
  • Accounts (Google, Chatwork, LINE, LINE Developer)

and it's free.

source code: kurab/notifyEvents

What's Google Apps Script?

It's a JavaScript-based programming language provided by Google that allows you to do a lot of things, and it's easy to integrate with Google's services.

Nowadays, even non-engineers can let Python do all the routine work for them. But with Python, it's a bit more difficult to get started in terms of having to install this and that. With Google Apps Script (GAS), on the other hand, all you need is a browser and a Google account, so even non-engineers can easily get started. Furthermore, there are many companies that use GSuite, so routine work may be relatively related to Google's services.

GAS is also basically JavaScript, so you can solve most of the problems by just Google it.

Hello, world

First of all, access to Google Drive. I'd like to work in "My Drive > bot" directory for this time.

Select "Google Apps Script" from "+New" menu on top left or "Right click" menu > more.

addScript

Then, you will move to the script editor like this.

gasEdit

Let name helloWorld as project name and update code like this:

function myFunction() {
  console.log('Hello, World');
}
Enter fullscreen mode Exit fullscreen mode

After saving your code(Ctrl+s or save button), click Run button(▶) on menu. It seems nothing happens but you can find log from menu "View > Logs".

gasLog

Congrats!! Now you have done your first Google Apps Script. This is the basic working flow of GAS.

Get Calendar Events

Here is the reference of Google Calendar API.

Calendar Service

This time, I'd like to get today's events information.

Let's create GAS file and name "notifyCalendar" or anything you want.

To start, access your calendar from script and get the calendar name.
Find Calendar ID in "Settings and sharing" of calendar > Integrate calendar > Calendar ID and paste it to {Calendar ID} in following code.

function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calenderId);
  const calendarName = calendar.getName();

  console.log(calendarName);
}
Enter fullscreen mode Exit fullscreen mode

When you run this, there will be a review of your calendar access and you will be prompted to grant it. Then wait for the execution to complete, open the log and you will see the calendar name. If there is no log yet, wait for a while.

Now, let's continue to get today's events in the calendar and put them in the log.

function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calenderId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);

  console.log(calendarEvents);
}
Enter fullscreen mode Exit fullscreen mode

If you have events today, you will get data like [{}, {}, {}]. If you don't have event, you will get empty array like [].

List up events if you have, and display "no event" if you don't have. Well, if you want to specify a period of time, not just today, use the getEvents method.

function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);

  if (calendarEvents.length) {
    for (var event of calendarEvents) {
      var eventTitle = event.getTitle() ? event.getTitle() : '(no title)';
      console.log(eventTitle);
    }
  } else {
    console.log('no event');
  }
}
Enter fullscreen mode Exit fullscreen mode

getTitle method doesn't return "(no title)" for no titled events. You need to set it by yourself.

Some events on your calendar are timed and some are not, so if you have a timed event, show it as well. At the same time, you don't want to get sent just the name of the event, so add a thoughtful message as well.

function notifyEvents() {
  const calendarId = '{Calendar ID}';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const calendarEvents = calendar.getEventsForDay(today);

  var message = 'Good morning!\n' +
                'Notification from ' + calendarName + 'at' +
                 Utilities.formatDate(today, 'GMT+0900', 'MM/dd') +
                '\n-----------------------------------------';

  if (calendarEvents.length) {
    for (var event of calendarEvents) {
      var eventTitle = event.getTitle() ? event.getTitle() : '(no title)';
      message = message + '\n- ' + eventTitle;
      if (!event.isAllDayEvent()) {
        message = message + ' at '+ 
                  Utilities.formatDate(event.getStartTime(), 'GMT+0900', 'HH:mm') + '~' + 
                  Utilities.formatDate(event.getEndTime(), 'GMT+0900', 'HH:mm') + '(JST)';
      }
    }
  } else {
    message = message + '\nno event';
  }

  console.log(message);
}
Enter fullscreen mode Exit fullscreen mode

We have successfully get calendar events now.
You can get more information by the API, check documents like getXX method or isXX.

Class CalendarEvent

Post to chat

Google Chat

Issue webhook url on chat room that you want to post calendar events.

chatWebhook

and paste it to {Webhook URL}.

function notifyEvents() {
    ...
  } else {
    message = message + '\nno evnet';
  }

  postToChat(message);
}

function postToChat(message) {
  const chatWebhook = '{Webhook URL}';
  const messageText = { 'text': message };
  const options = {
    'method': 'POST',
    'headers': {
      'Content-Type': 'application/json; charset=UTF-8'
    },
    'payload': JSON.stringify(messageText)
  };
  var result = UrlFetchApp.fetch(chatWebhook, options);
  Logger.log(result);
}
Enter fullscreen mode Exit fullscreen mode

You will ask to review access, so grant it.

Chatwork

Issue Chatwork API Token on Chatwork settings and memo chat room ID that you want to post. xxxxxx(number) of #!ridxxxxxx in URL.

Now on GAS editor, from menu > Resources > Libraries, add:

M6TcEyniCs1xb3sdXFF_FhI-MNonZQ_sT

I set the Latest version, 18.

chatworkLib

Paste your Chatwork API Token and Room ID.

function notifyEvents() {
  ...
  //postToChat(message);
  postToChatwork(message);
}

function postToChat(message) {...}

function postToChatwork(message) {
  const chatworkToken = '{Chatwork API Token}';
  const chatworkRoomId = '{Chatwork Room ID}';
  const chatworkClient = ChatWorkClient.factory({token: chatworkToken});
  chatworkClient.sendMessage({room_id: chatworkRoomId, body: message});
}
Enter fullscreen mode Exit fullscreen mode

LINE Messenger

This time, you can send a message only to yourself, so you can send the message to a fixed ID.

Flow is:

  1. on LINE Developers Create "Provider" and "Messaging API Channel"
  2. In Messaging API settings, issue "Channel access token (long-lived)"
  3. In Messaging API settings, disable "Auto-reply messages" and "Greeting messages" (Cuz we don't need them)
  4. In Basic settings, copy "Your user ID".
  5. On GAS, publish as Web App and copy URL (menu > Publish > Deploy as web app)
  6. On LINE Developers, in Messaging API settings, set URL that you copied at step5 as Webhook
  7. On your LINE Smartphone app, scan QR code to become a friend
  8. Run GAS

For a detailed explanation of LINE Developers, I'll leave you to look at other articles and so on, but the GAS code will be following. Put in the Token and user ID that you have copied in steps 2 and 4 above.

...
function postToLine(message) {
  const lineToken = '{LINE Token}';
  const lineMessagePushUrl = 'https://api.line.me/v2/bot/message/push';
  const lineHeaders = {
    "Content-Type": "application/json; charset=UTF-8",
    "Authorization": "Bearer " + lineToken
  };
  const linePayload = {
    "to" : "{User ID}",
    "messages" : [{
      "type": "text",
      "text": message
    }]
  };
  const options = {
    "method": "POST",
    "headers": lineHeaders,
    "payload": JSON.stringify(linePayload)
  };
  var result = UrlFetchApp.fetch(lineMessagePushUrl, options);
  Logger.log(result);
}
Enter fullscreen mode Exit fullscreen mode

Set Trigger and execute it every morning

Finally, let's set up automatic execution. When you click "Trigger" button next to "Run" button in the menu, you will be redirected to "Trigger Settings" page. Create a new Trigger.

In this case, I want this applet to notify only once every morning. About the mysterious range from 9am to 10am, I tried to run it for a few days and it's 9:46am and every day is different. I don't feel uncomfortable, but in my case, I thought it's good as long as the notification comes before the start of work.

gasTrigger

That's all, but... I'll get notifications on Saturday and Sunday too, which will ruin my weekend feeling. So I'll add a process to GAS to not send messages on Saturdays and Sundays. Holidays...I can do that, but this time, it's okay.

function notifyEvents() {
  const calendarId = 'kura@indigames.net';
  const calendar = CalendarApp.getCalendarById(calendarId);
  const calendarName = calendar.getName();

  const today = new Date();
  const dayOfWeek = today.getDay();
  if (dayOfWeek === 0 || dayOfWeek === 6) return;

  const calendarEvents = calendar.getEventsForDay(today);
  ...
}
...
Enter fullscreen mode Exit fullscreen mode

PS

When I googled "Google Apps Script", there are a lot of articles start from Spreadsheet..., and I had the impression that it is equivalent to the Macro of Excel, and it reminds me like the secret Excel Macro that no one can maintain.

Well, anyways, I think it's good to have the option of using Google Apps Script for small things.

Sample script for this article is here.

kurab/notifyEvents

Top comments (0)