port bot to rust, upgrade response, and cut away some of the tf cruft

This commit is contained in:
41666 2022-02-06 19:16:08 -05:00
parent 0a37eff047
commit f5fb729ce7
17 changed files with 1440 additions and 205 deletions

View file

@ -118,7 +118,7 @@ jobs:
id: docker
with:
context: .
file: ./hack/dockerfiles/${{matrix.dockerfile}}.Dockerfile
file: ./packages/${{ matrix.dockerfile }}/Dockerfile
push: true
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache

View file

@ -1,28 +0,0 @@
FROM node:16 AS builder
# Create the user and group files that will be used in the running container to
# run the process as an unprivileged user.
RUN mkdir /user \
&& echo 'nobody:x:65534:65534:nobody:/:' > /user/passwd \
&& echo 'nobody:x:65534:' > /user/group
# Set the working directory outside $GOPATH to enable the support for modules.
WORKDIR /src
# Fetch dependencies first; they are less susceptible to change on every build
# and will therefore be cached for speeding up the next build
COPY ./package.json ./yarn.lock /src/
COPY ./packages/bot/package.json /src/packages/bot/
RUN yarn workspace @roleypoly/bot install --focus
FROM node:16-slim AS final
WORKDIR /src
COPY --from=builder /user/group /user/passwd /etc/
USER nobody:nobody
# Import the code from the context.
COPY --from=builder /src/node_modules /src/node_modules
COPY ./packages/bot /src/packages/bot
ENTRYPOINT [ "node", "/src/packages/bot/index.js" ]

View file

@ -57,5 +57,5 @@ export const embedResponse = (
export const embedPalette = {
success: 0x1d8227,
error: 0xf14343,
neutral: 0x2c2f33,
neutral: 0x453e3d,
};

1
packages/bot/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
target

1283
packages/bot/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

12
packages/bot/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "bot"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serenity = { version = "0.10.10", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "unstable_discord_api", "collector"] }
dotenv = "0.15.0"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
url = "2.2.2"

22
packages/bot/Dockerfile Normal file
View file

@ -0,0 +1,22 @@
# Leveraging the pre-built Docker images with
# cargo-chef and the Rust toolchain
FROM lukemathwalker/cargo-chef:latest-rust-1.58.1 AS chef
WORKDIR /app
FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
# Build dependencies - this is the caching Docker layer!
RUN cargo chef cook --release --recipe-path recipe.json
# Build application
COPY . .
RUN cargo build --release --bin app
# We do not need the Rust toolchain to run the binary!
FROM gcr.io/distroless/base AS runtime
WORKDIR /app
COPY --from=builder /app/target/release/app /usr/local/bin
ENTRYPOINT ["/usr/local/bin/app"]

19
packages/bot/README.md Normal file
View file

@ -0,0 +1,19 @@
# Roleypoly Mention Responder
This is written in Rust.
You'll need:
- The rust toolchain (cargo, rust, etc)
...and nothing else.
## Premise of why Rust
Node.js is slow. It's fast enough for 90% of what we want to do, but due to the slowness and memory constraints we'd like to utilize, something else, particularly one designed for extreme multiprocessing (like Rust or Go) is infinitely better. More threads, more memory control (e.g. we can GC the majority of incoming info before we care about it), just better.
This was a very simple Node.js app, but it just couldn't be used with the production workload.
Roleypoly Legacy was running a Go-based bot that worked extremely well, and this iteration's de-evolution back to JS didn't end up working.
**tl;dr:** this piece of shit only responds to mentions. it has no real logic. it shouldn't take a $45/m cloud server to run it.

View file

@ -1,34 +0,0 @@
const { Client, Message } = require('discord.js');
const botToken = process.env['BOT_TOKEN'];
const allowedBots = process.env['ALLOWED_BOTS']?.split(',') ?? [];
const appUrl = process.env['UI_PUBLIC_URI'] ?? '';
function messageEventListener(message) {
const { author, channel, client, guild, mentions } = message;
if (!guild) {
return;
} // Ignore DMs
if (
client.user &&
!mentions.has(client.user.id, { ignoreRoles: true, ignoreEveryone: true })
) {
return;
} // Ignore non bot mentions
if (author.bot && !allowedBots.includes(author.id)) {
return;
} // Only respond to allowed bots
const guildId = guild.id;
channel.send({ content: `:beginner: Assign your roles here! ${appUrl}/s/${guildId}` });
}
const client = new Client({
intents: ['GUILDS', 'GUILD_MESSAGES'],
});
client.on('messageCreate', (message) => messageEventListener(message));
client.login(botToken);

View file

@ -1,12 +0,0 @@
const path = require('path');
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
const { ShardingManager } = require('discord.js');
const botToken = process.env['BOT_TOKEN'];
const manager = new ShardingManager(path.resolve(__dirname, 'bot.js'), {
token: botToken,
});
manager.spawn();

View file

@ -1,11 +1,7 @@
{
"name": "@roleypoly/bot",
"version": "0.1.0",
"version": "0.0.1",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"discord.js": "^13.6.0",
"dotenv": "^14.3.2"
"start": "cargo run"
}
}

89
packages/bot/src/main.rs Normal file
View file

@ -0,0 +1,89 @@
extern crate dotenv;
extern crate tokio;
use dotenv::dotenv;
use std::env;
use serenity::{
async_trait,
model::{
channel::Message,
gateway::Ready,
interactions::{
message_component::ButtonStyle,
},
},
prelude::*,
};
struct Handler {
ui_public_uri : String,
ui_hostname : String,
}
#[async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
// No DMs, bots, or self.
if msg.is_private() || msg.author.bot {
return;
}
// Ignore messages that don't mention the bot
if !msg.mentions_me(&ctx).await.unwrap_or(false) {
return;
}
msg.channel_id.send_message(&ctx, |m| {
m.reference_message(&msg).allowed_mentions(|f| {
f.replied_user(false)
});
m.embed(|e| {
e.title(":beginner: Howdy, pick your roles here!");
e.description("Roleypoly will open in your browser.\n\nIf that's not cool with you, try the `/roleypoly` command!");
e.url(&self.ui_public_uri);
e.color(0x453e3d);
e
});
m.components(|c| {
c.create_action_row(|r| {
r.create_button(|b| {
b.style(ButtonStyle::Link);
b.url(&self.ui_public_uri);
b.label(format!("Pick your roles on {}", self.ui_hostname));
b
});
r
});
c
});
m
}).await.unwrap();
}
async fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected! (shard: {:?})", ready.user.name, ready.shard.unwrap_or_default());
}
}
#[tokio::main]
async fn main() {
dotenv().ok();
let token = env::var("BOT_TOKEN").expect("BOT_TOKEN not set");
let client_id: u64 = env::var("BOT_CLIENT_ID").expect("BOT_CLIENT_ID not set").parse().unwrap();
let ui_public_uri = env::var("UI_PUBLIC_URI").expect("UI_PUBLIC_URI not set");
let ui_hostname = url::Url::parse(&ui_public_uri).unwrap().host_str().unwrap().to_string();
let mut client =
Client::builder(&token).application_id(client_id).event_handler(Handler {
ui_public_uri,
ui_hostname,
}).await.expect("Err creating client");
if let Err(why) = client.start_autosharded().await {
println!("Client error: {:?}", why);
}
}

View file

@ -1,12 +0,0 @@
locals {
artifactBaseMap = {
us-east4 = "us-docker.pkg.dev/roleypoly/roleypoly"
us-central1 = "us-docker.pkg.dev/roleypoly/roleypoly"
us-west1 = "us-docker.pkg.dev/roleypoly/roleypoly"
europe-west2 = "europe-docker.pkg.dev/roleypoly/roleypoly"
europe-west3 = "europe-docker.pkg.dev/roleypoly/roleypoly"
australia-southeast1 = "asia-docker.pkg.dev/roleypoly/roleypoly"
asia-northeast1 = "asia-docker.pkg.dev/roleypoly/roleypoly"
asia-southeast1 = "asia-docker.pkg.dev/roleypoly/roleypoly"
}
}

View file

@ -8,11 +8,6 @@ variable "environment_tag" {
}
}
variable "ui_regions" {
type = list(string)
description = "Cloud Run regions to deploy UI to"
}
variable "ui_tag" {
type = string
description = ":tag or @sha265: of *-docker.pkg.dev/roleypoly/roleypoly/ui"
@ -47,11 +42,6 @@ variable "ui_public_uri" {
description = "UI Public Base Path"
}
variable "ui_hostnames" {
type = list(string)
description = "Hostnames to allow web UI requests from, e.g. roleypoly.com, web-prod.roleypoly.com"
}
variable "api_public_uri" {
type = string
description = "API Public Base Path"

View file

@ -1,20 +1,6 @@
environment_tag = "prod"
ui_regions = [
"us-east4",
"us-central1",
"us-west1",
"europe-west2",
"europe-west3",
"australia-southeast1",
"asia-northeast1",
"asia-southeast1"
]
deploy_bot = true
bot_instance_size = "e2-medium"
ui_hostnames = [
"next.roleypoly.com",
"web-prod.roleypoly.com"
]
environment_tag = "prod"
deploy_bot = true
bot_instance_size = "e2-small"
ui_public_uri = "https://roleypoly.com"
api_public_uri = "https://api-prod.roleypoly.com"
allowed_callback_hosts = "https://roleypoly.com,https://next.roleypoly.com"

View file

@ -1,13 +1,6 @@
environment_tag = "stage"
ui_regions = [
"us-east4"
]
deploy_bot = true
bot_instance_size = "f1-micro"
ui_hostnames = [
"stage.roleypoly.com",
"web-stage.roleypoly.com"
]
environment_tag = "stage"
deploy_bot = true
bot_instance_size = "f1-micro"
ui_public_uri = "https://stage.roleypoly.com"
api_public_uri = "https://api-stage.roleypoly.com"
allowed_callback_hosts = "https://roleypoly.com,https://stage.roleypoly.com,https://*.roleypoly.pages.dev"
allowed_callback_hosts = "https://roleypoly.com,https://stage.roleypoly.com,https://*.roleypoly.pages.dev"

View file

@ -1198,22 +1198,6 @@
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4"
integrity sha512-M0qqxAcwCsIVfpFQSlGN5XjXWu8l5JDZN+fPt1LeW5SZexQTgnaEvgXAY+CeygRw0EeppWHi12JxESWiWrB0Sg==
"@discordjs/builders@^0.11.0":
version "0.11.0"
resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.11.0.tgz#4102abe3e0cd093501f3f71931df43eb92f5b0cc"
integrity sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg==
dependencies:
"@sindresorhus/is" "^4.2.0"
discord-api-types "^0.26.0"
ts-mixer "^6.0.0"
tslib "^2.3.1"
zod "^3.11.6"
"@discordjs/collection@^0.4.0":
version "0.4.0"
resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.4.0.tgz#b6488286a1cc7b41b644d7e6086f25a1c1e6f837"
integrity sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw==
"@discoveryjs/json-ext@^0.5.3":
version "0.5.6"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
@ -1872,16 +1856,6 @@
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323"
integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==
"@sapphire/async-queue@^1.1.9":
version "1.1.9"
resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.1.9.tgz#ce69611c8753c4affd905a7ef43061c7eb95c01b"
integrity sha512-CbXaGwwlEMq+l1TRu01FJCvySJ1CEFKFclHT48nIfNeZXaAAmmwwy7scUKmYHPUa3GhoMp6Qr1B3eAJux6XgOQ==
"@sindresorhus/is@^4.2.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.3.0.tgz#344fd9bf808a84567ba563d00cc54b2f428dbab1"
integrity sha512-wwOvh0eO3PiTEivGJWiZ+b946SlMSb4pe+y+Ur/4S87cwo09pYi+FWHHnbrM3W9W7cBYKDqQXcrFYjYUCOJUEQ==
"@sinonjs/commons@^1.7.0":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
@ -3204,7 +3178,7 @@
resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256"
integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==
"@types/node-fetch@^2.5.12", "@types/node-fetch@^2.5.7":
"@types/node-fetch@^2.5.7":
version "2.5.12"
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66"
integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw==
@ -6398,26 +6372,6 @@ discontinuous-range@1.0.0:
resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a"
integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
discord-api-types@^0.26.0:
version "0.26.1"
resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.26.1.tgz#726f766ddc37d60da95740991d22cb6ef2ed787b"
integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==
discord.js@^13.6.0:
version "13.6.0"
resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.6.0.tgz#d8a8a591dbf25cbcf9c783d5ddf22c4694860475"
integrity sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==
dependencies:
"@discordjs/builders" "^0.11.0"
"@discordjs/collection" "^0.4.0"
"@sapphire/async-queue" "^1.1.9"
"@types/node-fetch" "^2.5.12"
"@types/ws" "^8.2.2"
discord-api-types "^0.26.0"
form-data "^4.0.0"
node-fetch "^2.6.1"
ws "^8.4.0"
dlv@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79"
@ -6579,11 +6533,6 @@ dotenv@^10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
dotenv@^14.3.2:
version "14.3.2"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-14.3.2.tgz#7c30b3a5f777c79a3429cb2db358eef6751e8369"
integrity sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ==
dotenv@^8.0.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
@ -7733,15 +7682,6 @@ form-data@^3.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
@ -14754,11 +14694,6 @@ ts-loader@^9.2.6:
micromatch "^4.0.0"
semver "^7.3.4"
ts-mixer@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/ts-mixer/-/ts-mixer-6.0.0.tgz#4e631d3a36e3fa9521b973b132e8353bc7267f9f"
integrity sha512-nXIb1fvdY5CBSrDIblLn73NW0qRDk5yJ0Sk1qPBF560OdJfQp9jhl+0tzcY09OZ9U+6GpeoI9RjwoIKFIoB9MQ==
ts-node@^10.4.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
@ -14797,7 +14732,7 @@ tslib@^1.8.1, tslib@^1.9.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.3.1:
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
@ -15912,7 +15847,7 @@ ws@^7.4.6:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@^8.1.0, ws@^8.2.2, ws@^8.2.3, ws@^8.4.0:
ws@^8.1.0, ws@^8.2.2, ws@^8.2.3:
version "8.4.2"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.2.tgz#18e749868d8439f2268368829042894b6907aa0b"
integrity sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==
@ -15995,11 +15930,6 @@ youch@^2.2.2:
mustache "^4.2.0"
stack-trace "0.0.10"
zod@^3.11.6:
version "3.11.6"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483"
integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==
zwitch@^1.0.0:
version "1.0.5"
resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"