Quantcast
Channel: Stefan Bohacek
Viewing all articles
Browse latest Browse all 71

Making a Mastodon bot with Google Sheets and Apps Scripts

$
0
0

A tinted collage screenshot of a donations table in Google Sheets overlaid with a post from a bot summarizing the total amount of donations.

I only learned about Google Apps Scripts relatively recently, and as a big fan of spreadsheets, I started thinking about ways to use this tool with Google’s own Sheets service. And what’s a better use of spreadsheets than setting up an automated account on Mastodon that will post data from it!

Before we start, let’s create an account for our bot. I wrote a step-by-step process over on Botwiki to walk you through it. At the end, you will end up with an access token, which will need later on. Also take note of the URL of the server you made your bot account on.

You can now create our Google Sheets spreadsheet, or use an existing one if you already have it. I’m just going to populate mine with some random data about donations, you feel free to use whatever works for you.

DateAmountTotal
2024-01-24$59.78
2024-02-02$41.03
2024-02-04$67.58
2024-02-06$3.27
2024-02-11$61.96

A nice thing about using spreadsheets is that you can do a lot of the “programming” work here instead of with code, saving yourself a ton of time.

For example, in the Total column, I can add =SUM(B:B) to get a total amount of donations.

DateAmountTotal=SUM(B:B)
2024-01-24$59.78
2024-02-02$41.03
2024-02-04$67.58
2024-02-06$3.27
2024-02-11$61.96

Easy as that.

Now, let’s navigate to Extensions -> Apps Script in the main Sheets menu. First, we’ll need to set a few things up. Head over to the Project Settings, marked with a cog icon in the left-hand sidebar. In the Script Properties section, we will add two items:

  • MASTODON_API_URL: this will be the URL of the API endpoint of the instance you made your bot on. For example, if your bot will be posting on mastodon.social, the value will be https://mastodon.social/api/v1/
  • MASTODON_ACCESS_TOKEN_SECRET: this is the token you obtained when setting up your bot’s account

Great. With these in place, let’s start writing our script. Go to the editor (look for the <> icon in the sidebar) and replace the code with this:

function postToMastodon() {
  try {
    const scriptProperties = PropertiesService.getScriptProperties();
    const accessToken = scriptProperties.getProperty('MASTODON_ACCESS_TOKEN_SECRET');
    const apiUrl = scriptProperties.getProperty('MASTODON_API_URL');

    const response = UrlFetchApp.fetch(`${apiUrl}/statuses`, {
      "method": "POST",
      "contentType": "application/json",
      "payload": JSON.stringify({
        "status": "Hello world!"
      }),
      "headers": {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": "Bearer " + accessToken,
      }
    });

    console.log(response.getContentText());
  } catch (err) {
    console.log(`there was an error: ${err.message}`);
  }
}

Note that we’re using Mastodon’s statuses endpoint to post our message.

Save and run this code. You might be prompted to allow access to your account if this is the first time you’re using Apps Scripts, so go ahead and confirm this.

Once the code runs, you should see a post made by your bot matching the value of the status field. You will also see the response from the Mastodon server in your script’s execution log. So far so good!

Now for the fun part.

To get the value of our sum, we can use the following function:

const row = 1;
const col = 5;
const sum = SpreadsheetApp.getActiveSheet().getRange(row, col).getValue();

Our script will now look like this.

function postToMastodon() {
  try {
    const scriptProperties = PropertiesService.getScriptProperties();
    const accessToken = scriptProperties.getProperty('MASTODON_ACCESS_TOKEN_SECRET');
    const apiUrl = scriptProperties.getProperty('MASTODON_API_URL');

    const row = 1;
    const col = 5;
    const sum = SpreadsheetApp.getActiveSheet().getRange(row, col).getValue();

    const response = UrlFetchApp.fetch(`${apiUrl}/statuses`, {
      "method": "POST",
      "contentType": "application/json",
      "payload": JSON.stringify({
        "status": `The total sum of donations is $${sum}.`
      }),
      "headers": {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": "Bearer " + accessToken,
      }
    });

    console.log(response.getContentText());
  } catch (err) {
    console.log(`there was an error: ${err.message}`);
  }
}

Let’s run the script again (you might be prompted with another access request), and we should see our bot posting the total.

An example Mastodon post from the "Stefan's test bot" account @fediversebot. The text says:

"The total sum of donations is $2848.2899999999995."

The bot uses a smiling emoji with closed eyes as the profile picture.

Alright, that’s not too bad. Let’s maybe format the number a bit better.

function postToMastodon() {
  try {
    const scriptProperties = PropertiesService.getScriptProperties();
    const accessToken = scriptProperties.getProperty('MASTODON_ACCESS_TOKEN_SECRET');
    const apiUrl = scriptProperties.getProperty('MASTODON_API_URL');

    const row = 1;
    const col = 5;
    const sum = SpreadsheetApp.getActiveSheet().getRange(row, col).getValue();

    const USDollar = new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD",
    });

    const response = UrlFetchApp.fetch(`${apiUrl}/statuses`, {
      "method": "POST",
      "contentType": "application/json",
      "payload": JSON.stringify({
        "status": `The total sum of donations is ${USDollar.format(sum)}.`
      }),
      "headers": {
        "Accept": "application/json",
        "Content-Type": "application/json",
        "Authorization": "Bearer " + accessToken,
      }
    });

    console.log(response.getContentText());
  } catch (err) {
    console.log(`there was an error: ${err.message}`);
  }
}
An example Mastodon post from the "Stefan's test bot" account @fediversebot. The text says:

"The total sum of donations is $2,848.29."

The bot uses a smiling emoji with closed eyes as the profile picture.

There we go, much better.

And as the last step, we have a choice to make. We can either have the bot post whenever the spreadsheet is updated, or post on a schedule, say, every few hours, or once a day.

This can be managed on the Triggers page (the sidebar icon shows a clock for it).

Setting up a timed event is fairly straightforward with the following settings for your trigger:

  • Choose which function to run: postToMastodon
  • Choose which deployment should run: Head (this means the latest version of our script)
  • Select event source: Time-driven

And then choose the appropriate settings for the Type of time based trigger and

I went ahead and set mine up to run once every minute to try things out, and when I visit the Executions page from the sidebar, I do indeed see the script running about every 60 seconds.

If you want the bot to post every time you make a change, here are the settings for your trigger.

  • Choose which function to run: postToMastodon
  • Choose which deployment should run: Head
  • Select event source: From spreadsheet
  • Select event type: On edit

This Stack Overflow answers goes into detail comparing the differences between On edit and On change trigger events, but in short, On edit will work fine for us here.

Now, the tricky part is, we don’t really want to post every time the spreadsheet is updated, only when the sum changes. Adding a new date in the date column, for example, does not change the sum, so our bot would post both when we fill out a new date, and also when we add the donation for that date. So this will require a bit more coding.

When our function gets triggered by an On edit event, it will receiver data from that event.

function postToMastodon(event) {
  const range = event.range;

We can then find out which cell was edited by looking at columnStart, columnEnd, rowStart, and rowEnd variables inside range.

function postToMastodon(event) {
  const range = event.range;

  if (range.columnStart === 2 && range.columnEnd === 2){
    // someone updated the second column, which has our donation information, we can post now
  } else {
    // skip
  }

And here is our finished function.

function postToMastodon(event) {
  try {
    if (event) {
      const range = event.range;
      console.log(
        `cell in column ${range.columnStart} and row ${range.rowStart} was updated`
      );

      if (range.columnStart === 2 && range.columnEnd === 2) {
        const scriptProperties = PropertiesService.getScriptProperties();
        const accessToken = scriptProperties.getProperty(
          "MASTODON_ACCESS_TOKEN_SECRET"
        );
        const apiUrl = scriptProperties.getProperty("MASTODON_API_URL");

        const row = 1;
        const col = 5;
        const sum = SpreadsheetApp.getActiveSheet()
          .getRange(row, col)
          .getValue();

        const USDollar = new Intl.NumberFormat("en-US", {
          style: "currency",
          currency: "USD",
        });

        const response = UrlFetchApp.fetch(`${apiUrl}/statuses`, {
          method: "POST",
          contentType: "application/json",
          payload: JSON.stringify({
            status: `The total sum of donations is ${USDollar.format(sum)}.`,
          }),
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
            Authorization: "Bearer " + accessToken,
          },
        });

        console.log(response.getContentText());
      } else {
        console.log("skipping...");
      }
    } else {
      console.log("no data was sent");
    }
  } catch (err) {
    console.log(`there was an error: ${err.message}`);
  }
}

Hope you find this tutorial useful! Be sure to visit botwiki.org for more creative botmaking resources.

Until next time!


Viewing all articles
Browse latest Browse all 71

Trending Articles