User:Slate/Code/Discord and App Scripts notes

From Inkipedia, the Splatoon wiki

Discord is a chatting service that a lot of the Splatoon community use. Google App Scripts (GAS) can automated certain processes around the Google infrastructure such as Forms, Sheets, Docs etc. GAS uses Google-flavoured Javascript code.

I tried to tie the two together with some degree of success.

What can't be done

I'll preface this by saying this is what I've tried, and there may well be something I'm missing. I have given it a good stab though.

Sub-problems faced (and solutions)

To try and fix the 1020 error, I specified a User Agent, with no success. e.g. "User-Agent": "DiscordBot (https://discord.js.org 13.5.0-dev) Node.js/16.13.1"

Discord expects "application/x-www-form-urlencoded", and if you violate this you get a very unhelpful "unexpected grant_type: None".

The common approach is to use URLSearchParams for the POST payload. But that's not available in a GAS. You can use something like

/* Build a URL query options string like URLSearchParams (unavailable in GScript). Does not have a leading ?. */
function urlQueryBuilder(obj) {
  return Object.keys(obj).reduce(function(p, e, i) {
    return p + (i == 0 ? "" : "&") +
      (Array.isArray(obj[e]) ? obj[e].reduce(function(str, f, j) {
        return str + e + "=" + encodeURIComponent(f) + (j != obj[e].length - 1 ? "&" : "")
      },"") : e + "=" + encodeURIComponent(obj[e]));
  },"");
}

to work round this.

The Discord documentation does not specify what "code" is, but some websites recommend a valid URL. I used a Twitter URL. The documentation is also not clear on "grant_type" -- some requires "client_credentials" and others "authorization_code". Scope (complete list) is usually set to "bot", though if it's an application like in this case, you do not need all the bot scope.

  const params = urlQueryBuilder({
    "client_id": clientId,
    "client_secret": secret,
    "grant_type": "client_credentials",
    "scope": "identify guilds.members.read",
    "code": code
  });

to give something like

const r = JSON.parse(UrlFetchApp.fetch(url, {
    method: 'POST',
    muteHttpExceptions: true,
    headers: {
        "Content-type": "application/x-www-form-urlencoded"
    },
    payload: params
  }).getContentText());

(muteHttpExceptions: true spells out http errors), but then

let result = /*r["token_type"]*/ "Bot" + " " + r["access_token"];

runs into another problem -- for bots, the bearer token is different -- it must be prefixed with "Bot". BUT the token_type on the return does not specify that, even if asked with Bot credentials (Bearer is usually returned).

What can be done

  • Automated edits of the spreadsheet.
  • Pulling spreadsheet data from the Discord bot.
  • Posting to a Discord channel via the webhooks feature.
    • Sending request to bots in Discord
    • Converting data and posting to Discord, e.g. making a Google calendar.
  • Integration of the "Widget" functionality of a server -- this allows for counting of members, but does not get discord ids or discriminators, and so users can only be identified by their current name.

Proposed solutions

  • Have a separate bot that monitors the webhooks channel. When a request comes in via a message, the bot will separately connect to the spreadsheet and make modifications as appropriate.