Blog

Build a Telegram chat bot for ChatGPT with Google Apps Script

today March 9, 2023

In my previous post, we saw how to use Google Apps Script to read real-estate property highlights from a Google Sheet, feed them to ChatGPT, and store the property summaries that ChatGPT wrote for us in Google Docs. Subsequently, I received a request on my YouTube channel to do a follow-up tutorial on how to build a Telegram chat bot that interacts with ChatGPT using Apps Script. Well, I think that it's a great idea, so let's see how to do that.

First, lets review the data flow of this solution:

  • The Telegram user sends an update to our Telegram bot.
  • The bot automatically forwards the update to our Apps Script web app, which is registered as the bot's webhook (more on that later).
  • Our web app parses the user's text and forwards it as a prompt to ChatGPT.
  • ChatGPT sends its response back to our web app.
  • Our web app sends a Telegram message to the chat ID associated with the user's update.

Ok, now that we understand the data flow, let's lay out the tasks we need to accomplish:

  • Register a Telegram bot and obtain its API token
  • Get an OpenAI API key
  • Accept incoming chat updates
  • Send response message
  • Deploy the web app
  • Register the web app as a Telegram webhook
  • Integrate ChatGPT

Register a Telegram bot

This step is easy and doesn't require any coding. Telegram offers a very nice way to register new bots using their own "botFather" bot. Once you've created your free Telegram account, all you need to do is access https://t.me/botFather and issue a couple of updates: first, type "/newbot". This will prompt you to enter a name for bot. Next, you will be prompted to enter a user name. Once you do that, you wil receive an API token that you will incorporate later in the script when making REST API calls to Telegram. For now, save the token in a safe place and don't share it with anyone.

Get an OpenAI API key

Next, you need to create an account with OpenAI and get a key for its own API. See my previous post for instructions on how to do that.

Accept incoming chat updates

For this project, all you need is one Google Apps Script file. Once created, access the Script properties and add two properties, one for the Telegram key and the other for openAi. I named mine "telegramApiKey" and "telegramApiKey". Paste the two keys you received from Telegram and OpenAI.

Time to write some code. we need to create a web app that we will later register as the webhook of our Telegram bot. The idea here is that Telegram will graciously forward any update that any user sends to our bot to our web app as a POST request. So we need to define the doPost function that Apps Script will run automatically whenever it receives a post request:

function doPost(e) {
  if (!e.postData || !e.postData.contents) {
    return;
  }
  const update = JSON.parse(e.postData.contents);
  const chatId = update.message.chat.id;
  const updateText = update.message.text;
  if (!updateText || updateText.toString().trim().length === 0) {
    sendMessage(`Please use text messages with me`, chatId);
    return;
  }
  const message = processUpdate(updateText);
  sendMessage(message, chatId); 
}

Above is the function that Apps Script will execute automatically, passing into it the event object that is part of the POST request. First, we do a quick sanity check to see of the request contains our necessary payload (e.postData.contents). If it doesn't then we exit the function which will enable Telegram to send another request that hopefully will have payload.

Next, we parse out the request payload and extract that chat ID that is associated with the user update and the update text. We need the chat ID in order to send our updates to the right conversation.

The function invokes two other functions that we haven't defined yet: sendMessage, which communicates back to Telegram, and processUpdate that communicates with ChatGPT. Let's create a dummy "processUpdate" function just to get something back quick:

function processUpdate(prompt) {
  return `You said: ${prompt}`;
}

Send response message

We'll populate the ChatGPT interaction later; for now, we just echo the user update. Let's work on sending updates back to Telegram:

function sendMessage(text, chat_id) {
  const scriptProps = PropertiesService.getScriptProperties();
  const key = scriptProps.getProperty('telegramApiKey');
  const url = `https://api.telegram.org/bot${key}/sendMessage`;
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    muteHttpExceptions: true,
    payload: JSON.stringify({
      text,
      chat_id,
    }),
  };
  const response = UrlFetchApp.fetch(url, options);
  const content = response.getContentText();
  if (!response.getResponseCode().toString().startsWith('2')) {
    console.log(content);
  }
  return ContentService.createTextOutput('OK').setMimeType(
    ContentService.MimeType.TEXT
  );
}

Above, we get our Telegram API key from script properties, and embed it in the Telegram API URL, per their specification. We define an "Options" object that includes a stringified object with our text and the chat id. That's all Telegram needs to push our update into the right conversation.

Next, we use "UrlFetchApp" to send the payload to the Telegram API, and inspect the response. Finally, we return an OK to Telegram to let it know that we processed its POST request successfully.

Create a webapp

Ok, time to deploy our Apps Script as a public web app, so that Telegram will be able to call it later. In the Apps Script IDE, click the blue "Deploy" button. Click the gear icon on the left and select "Web app." Add a description, ensure that the script executes as "Me", and set "Who has access" to anyone. Click "Deploy" and copy the resulting web app URL.

Let's create a function to register our webapp as a Telegram webhook:

function registerWebhook() {
  const scriptProps = PropertiesService.getScriptProperties();
  const key = scriptProps.getProperty('telegramApiKey');
  let url = `https://api.telegram.org/bot${key}/getWebhookInfo`;
  let response = UrlFetchApp.fetch(url);
  let content = response.getContentText();
  let jsn = JSON.parse(content);
  if (!jsn.result || jsn.result.url.trim().length > 0) {
    console.log(jsn);
    return;
  }
  url = `https://api.telegram.org/bot${key}/setWebhook`;
  const options = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    muteHttpExceptions: true,
    payload: JSON.stringify({
      url: 'paste-your-webapp-url-here',
    }),
  };
  response = UrlFetchApp.fetch(url, options);
  content = response.getContentText();
  console.log(content);
}

The first part of the function is optional: we're checking to see if the bot already has a webhook registered, and if it does have one then we exit the function. We execute the check by issuing a GET request to the "/getWebhookInfo" endpoint. If returns an object with a results object that has a url attribute. If no webhook is registered then the url will be populated with our web app link, which is our indication to exit the function.

To register the webhook, we set our url to the "/setWebhook" method. In the payload, we include the URL to the web app by pasting it there. Note that Apps Script provides a method to get the URL of a published web app programmatically:

ScriptApp.getService().getUrl()

but in my experience, the method doesn't always work well, so we paste the URL manually.

If you run registerWebhook, you will see the result object listed in the console, with the URL of your webapp. At this point, you can actually test out Telegram: simply search for the bot in the Telegram interace and type something. You should see "You said: ..." with whatever you entered sent back to you.

Integrate ChatGPT

Let's get the AI involved. The revised processUpdate below sends the Telegram text to ChatGPT as a prompt and returns the content that ChatGPT creates:

function processUpdate(prompt) {
  const scriptProps = PropertiesService.getScriptProperties();
  const key = scriptProps.getProperty('chatGptApiKey');
  const url = 'https://api.openai.com/v1/completions';
  const options = {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${key}`,
      'Content-Type': 'application/json',
    },
    muteHttpExceptions: true,
    payload: JSON.stringify({
      prompt,
      model: 'text-davinci-003',
      // model: 'gpt-3.5-turbo',
      temperature: 1,
      max_tokens: 150,
    }),
  };
  const response = UrlFetchApp.fetch(url, options);
  const content = response.getContentText();
  const jsn = JSON.parse(content);
  if (jsn.choices && jsn.choices[0]) {
    return jsn.choices[0].text;
  } else {
    return 'Please try again';
  }
}

Above, we get the ChatGPT API key and integrate it into our "Options" object as an Authorization header. We also define the payload for ChatGPT. Note that while we use the "text-davinci-003" model, OpenAI just announced a new "gpt-3.5-turbo" model. I haven't tested it yet though, so I'm sticking with davinci here.

We sent the request to chatGPT using "UrlFetchApp" and inspect the response. The text is included inside the first element of the "choices" array using the "text" property. If it exists then we send it back.

Now that we included the revised function, we need to redeploy the app. Click on the "Deploy" button and thins time select "Manage deployments." Don't create a new deployment because that will create a new web app URL that will be different than the one registered with Telegram. Click the pencil icon. Under "Version," select "New version." You can also add a new description. Click "Deploy."

That's it. Now, if you send a Telegram update, you should receive the ChatGPT response. Pretty nifty, right?

Happy telegramming!