All this to run shell commands from discord

things-that-I-thought-were-interesting-while-building-this-follow-up-post day 1!

so I wanted to build this, a discord bot that interacts with glitch. so we’d better get two parts set up:

  1. code that interacts with Glitch
  2. code that acts as a Discord bot

code that interacts with Glitch is easy. there’s an API that lets you run a command. we have code for it. done.

ok, on to code that acts as a Discord bot.

this had classically been a big problem. in 2020, you had to connect from your bot to Discord, so you had to have your project on all the time. and Glitch later added a premium always-on option that would let you connect to Discord this way.

but in the time since then, Discord has added an “interaction” webhook system where Discord would connect to your bot. that’s even better, so that Glitch can wake up your project to handle the interaction and then put it back to sleep afterwards.

Discord offers a Node.js SDK for building with interactions, so we’re all set. easy. I’d even go so far as to say peasy. let’s go get it.

some steps later, wait a minute

oh no


tune in tomorrow for why this is set so high and what we can do about it

2 Likes

things-that-I-thought-were-interesting-while-building-this-follow-up-post day 2!

so here we are at the Discord (official?) library for handling these interaction webhook requests, and it requires a version of Node.js higher than what we have available on Glitch. what do we do now?


(continuity note: I’m writing this on a different computer today)

in the last post, I said I would discuss why that version is set “so high.” in fact it is not very high. Node.js 18.x is the oldest release that is still being maintained by the authors. in some cases, a library might have its minimum node version bumped to whatever the oldest supported version is even without requiring any new features [citation needed]. so one thing we might like to do is to look at the circumstances for this library having its nod minimum node version bumped.

here we’re heading off to software archaeology land. I find that I do this pretty often, but it occurs to me we might have some folks in the audience who are new to it. so let’s take some time to show how to actually go to software archaeology land, find what we need, and come back alive and wiser than when we set off.

we are currently on GitHub, which you may understand from its name, hosts repositories that are created by the version control program, Git. Git has a command called blame for looking up who last changed a given line and when. GitHub allows us to look at this same information on a web page.

we’ll first look at this information to find out when the library developers changed this line containing the minimum node version. to do that, we’ll click this button up here to go to the blame view. (folks who know the leet-er ways to do this, have at it in the replies.) similarly if we were exploring this repository locally with the git command line tool, we’d run git blame package.json.

that takes us to a view where each line has its blame information shown to the left of it. let’s go back down to the minimum node version line and consult its blame information.

this line last changed 9 months ago, in a Git commit whose title is shown. our next step will be to read the commit to learn about the circumstances of the change. on GitHub, we navigate to the commit by clicking that commit title. similarly if we were on the command line, a short commit hash would be shown on that line and we’d run git show 69499c to see the commit.

the title is “fix: remove dependency on tweetnacl,” and the diff below shows that they indeed remove the tweetnacl package from the dependencies. tweetnacl is a cryptography library which they had used to verify the Discord server’s digital signature on incoming webhook requests. as part of removing tweetnacl, they wrote a new signature verification routine that uses…

the web crypto API. and that API is provided by Node.js itself, which makes the availability of the different cryptographic algorithms very sensitive to the Node.js version. so was this a frivolous requirement bump? no. does the library really need this version? yeah. rats.

one last thing to note before we pack it up and go home. the cryptography library that the Discord library was switching away from is tweetnacl.

image

the tweetnacl library on npm doesn’t have a minimum Node.js version declared in its package.json. but I’d guess that it has pretty good compatibility with older versions. behold, it hasn’t bumped its major version since 2017.

that’s good to know. we can keep that in mind as an idea to implement the cryptographic operations in a way that’s compatible with the Node.js 16 or below that we have on Glitch.

for anyone who wondered “does it matter if I make good commits with meaningful messages?” it does. now let’s go back.

back to programming. let me summarize what we found out:

  • yes the discord interactions library really does need that minimum Node.js version
  • it uses the web crypto API
  • they had code that’s compatible with earlier versions of Node.js based on the tweetnacl library

so we’ll need to put together a library that’s compatible with at most Node.js 16, and it seems we’ll need two parts for that:

  1. most of the stuff from the discord interactions library that’s already compatible with older Node.js
  2. another way to verify a digital signature

and we’re aware that the tweetnacl library offers a way to verify that digital signature.


tune in tomorrow for uh, I think I’ll write a little about building this new discord interactions library

3 Likes

things-that-I-thought-were-interesting-while-building-this-follow-up-post day 3!

the digital signature verification algorithm that Discord needs is called Ed25519. a new enough web cryptography API supports this, and the tweetnacl library supports this. but guess what?

Node.js’s crypto module supports this too, and as far back as Node.js 12 :eyes:

so we have on the one hand, the audited, popular, well respected library tweetnacl, and for which we have code from a previous version of Discord’s library showing us how to use it. on the other hand, we have this Node.js crypto API that could potentially save us one dependency.

folks, I went with the crypto API :smiling_face_with_tear:

because how hard could it be? there’s a single crypto.verify method…

well it was a little fiddly to get the public key imported right. after a certain amount of time researching the so called spki format that Node.js expects the key to be in, the verification was working.

if you’re interested to use this Discord signature verification middleware to make your own bot, I can open source it.

anyway, with the verification working, the bot passed Discord’s startup checks, and the rest of the functionality took shape.

at this point, the bot was working … for about five good minutes. when I tried using it again later, the project was asleep, and Discord started complaining that the app didn’t respond.

from Discord’s docs:

Interaction tokens are valid for 15 minutes and can be used to send followup messages but you must send an initial response within 3 seconds of receiving the event. If the 3 second deadline is exceeded, the token will be invalidated.

oh no

and, like, I tried optimizing a few things to make the project start up faster, but eh it still seems to take over 3 seconds. although let me know if you’ve succeeded at getting a Glitch project to wake up in less than 3 seconds.

so I gave up on this project for a while.


tune in next time—possibly tomorrow—for a tutorial on either how to order Glitch Pro or how do whatever else I ended up doing

1 Like

a very un-tomorrow things-that-I-thought-were-interesting-while-building-this-follow-up-post day 4!

to recap, Discord was all like “you’re taking more than 3 seconds to respond to an interaction? get outta here! if you can’t compute the digital signature thing and respond instantly, then you don’t get to have a bot.”

compute? instant??

free tier??? ohoho count me in. time to use a trial of a $500/mo service to avoid paying for a $8/mo solution.

it takes a little fiddling to set up a development environment on Glitch:

  1. the fastly CLI on glitch is an old version. I’m not sure if it still works for some purposes, but I might have been getting issues about a manifest version being wrong or something. anyway, the latest version is statically linked and doesn’t seem to run into any problems regarding new syscalls. I downloaded a copy into /app/.local/bin and it works fine.
  2. the new version’s fastly compute init command sets up a project, when using Rust, that specifies a newer version of Rust than we have. but with some environment variables, you can have rustup install that version. bless the Rust maintainers, they still support as low as glibc 2.17 :place_of_worship: (Glitch has glibc 2.23 installed). curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | RUSTUP_INIT_SKIP_PATH_CHECK=yes RUSTUP_HOME=/tmp/rustup CARGO_HOME=/tmp/cargo sh
  3. there isn’t enough room in /app to store all the intermediate stuff in /app/target, so I also had to set CARGO_TARGET_DIR=/tmp/target when doing fastly compute build.
  4. to do fastly compute serve, you need Fastly’s viceroy program, which emulates Fastly Compute’s environment locally. the fastly tool downloads a copy, but it’s built for a newer version of glibc than we have, and it won’t run. cargo install viceroy builds a working copy, which you can cp /tmp/cargo/bin/viceroy ~/.config/fastly.
  5. apparently the fastly tool also downloads a program called wasm-tools, which it also downloads a copy built for a newer version of glibc. but the fastly tool seems to tolerate a nonfunctional wasm-tools program, so I left it like that. maybe cargo install wasm-tools would work too.

and at that point, I was ready to play around with Fastly Compute on Glitch.

project link: Glitch :・゚✧


tune in next time for microservice architecture tomfoolery

2 Likes

things-that-I-thought-were-interesting-while-building-this-follow-up-post day 5!

we don’t have to rewrite the whole bot in Rust. (although how cool would that be!) Discord’s requirement is:

Interaction tokens are valid for 15 minutes and can be used to send followup messages but you must send an initial response within 3 seconds of receiving the event.

(https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-callback)

which is to say we don’t have to have a logic in this component that responds instantly from Fastly Compute. Discord allows us to give a response followed by more responses and/or edits later. all we have to do in our Rust program is:

  1. check digital signature and reject (HTTP 401) un-authentic requests
  2. reply to each request with a “thinking…” sort of thing (Discord interaction callback DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE)
  3. forward the request to the rest of the bot

and then when the Glitch project wakes up, it handles the request and tells Discord to replace the “thinking…” with the actual response.

I built the following simple behaviors for various Discord interaction types:

  • PING request → respond with PONG, don’t even bother the Glitch project
  • APPLICATION_COMMAND request → respond with ephemeral DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE, forward request
  • MESSAGE_COMPONENT request → respond with DEFERRED_UPDATE_MESSAGE, forward request
  • APPLICATION_COMMAND_AUTOCOMPLETE request or MODAL_SUBMIT request → uh pretty much only forward request :grimacing:

and then I updated the rest of the bot to assume that a “thinking…” message already existed and to edit that instead.

the part that runs on Fastly Compute is pretty general, so I might be able to reuse it if I need to make another Discord bot.


that’s about all for the interesting stuff. I’ll leave you with an inventory of uninteresting stuff too:

  • Discord’s API for replying to an interaction is kind of annoying. replying the first time and replying subsequent times need to go through different API endpoints. so if you ever have logic that conditionally sends a message, you need to keep track of which one to use where :skull:
  • I wanted to put the output in a code block, but there’s no way to escape a ``` triple backtick inside a code block. some folk advice on the internet says to put invisible unicode characters in between, but that feels too weird for me :skull: :skull:
  • the maximum message size of 2,000 characters feels really small when it comes to command outputs :skull: :skull: :skull:
  • Discord has an “ansi” code highlight “language” that renders those terminal color control sequences as color :rainbow:
3 Likes