Add working bot

This commit is contained in:
Sam W 2023-05-11 14:13:38 +01:00
parent fb6cfa8e08
commit 83c753add8
7 changed files with 2095 additions and 0 deletions

2
.envrc Normal file
View File

@ -0,0 +1,2 @@
use flake
source .envrc.local

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
.direnv
.envrc.local

1670
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

10
Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "rolebot"
version = "0.1.0"
edition = "2021"
[dependencies]
serenity = { version = "0.11.5", default-features = false, features = ["builder", "cache", "chrono", "client", "gateway", "model", "http", "utils", "rustls_backend"] }
tokio = { version = "1.28.0", features = ["rt-multi-thread"] }
tracing = "0.1.37"
tracing-subscriber = "0.3.17"

210
flake.lock Normal file
View File

@ -0,0 +1,210 @@
{
"nodes": {
"devshell": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1682700442,
"narHash": "sha256-qjaAAcCYgp1pBBG7mY9z95ODUBZMtUpf0Qp3Gt/Wha0=",
"owner": "numtide",
"repo": "devshell",
"rev": "fb6673fe9fe4409e3f43ca86968261e970918a83",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1642700792,
"narHash": "sha256-XqHrk7hFb+zBvRg6Ghl+AZDq03ov6OshJLiSWOoX5es=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "846b2ae0fc4cc943637d3d1def4454213e203cba",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"naersk": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1679567394,
"narHash": "sha256-ZvLuzPeARDLiQUt6zSZFGOs+HZmE+3g4QURc8mkBsfM=",
"owner": "nix-community",
"repo": "naersk",
"rev": "88cd22380154a2c36799fe8098888f0f59861a15",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "naersk",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1677383253,
"narHash": "sha256-UfpzWfSxkfXHnb4boXZNaKsAcUrZT9Hw+tao1oZxd08=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9952d6bc395f5841262b006fbace8dd7e143b634",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1678101631,
"narHash": "sha256-vuuvWBNGhNSPPbFCjp2XZmBqJOvsFF1T0hyleRnHZlc=",
"path": "/nix/store/9hi0ykjc2h1hzldcqdfmjgw2hp0lld2v-source",
"rev": "934e613c31cf7af0624dcf088b9e2d9b802d0717",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1678101631,
"narHash": "sha256-vuuvWBNGhNSPPbFCjp2XZmBqJOvsFF1T0hyleRnHZlc=",
"path": "/nix/store/9hi0ykjc2h1hzldcqdfmjgw2hp0lld2v-source",
"rev": "934e613c31cf7af0624dcf088b9e2d9b802d0717",
"type": "path"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1681358109,
"narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"devshell": "devshell",
"naersk": "naersk",
"nixpkgs": "nixpkgs_3",
"rust-overlay": "rust-overlay",
"utils": "utils"
}
},
"rust-overlay": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1683426137,
"narHash": "sha256-iqLjaJDMppvK1ae1eL55b2r7EX+rbUuEnssFOe9eOm8=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "546a8209ce0965475495dd422e4ab3c6de7a268c",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

52
flake.nix Normal file
View File

@ -0,0 +1,52 @@
{
description = "A discord bot that allows users to add and remove themself to roles";
inputs = {
utils.url = "github:numtide/flake-utils";
devshell.url = "github:numtide/devshell";
naersk.url = "github:nix-community/naersk";
rust-overlay.url = "github:oxalica/rust-overlay";
};
outputs = {
self,
nixpkgs,
utils,
naersk,
devshell,
rust-overlay,
}:
utils.lib.eachDefaultSystem (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [(import rust-overlay)];
};
rust = (pkgs.rust-bin.stable.latest.default.override {
extensions = [ "rust-src" ];
});
# Override naersk to use our chosen rust version from rust-overlay
naersk-lib = naersk.lib.${system}.override {
cargo = rust;
rustc = rust;
};
in rec {
packages.default = naersk-lib.buildPackage {
pname = "rolebot";
root = ./.;
};
apps.default = utils.lib.mkApp {drv = packages.default;};
# Provide a dev env with rust and rls
devShells.default = let
pkgs = import nixpkgs {
inherit system;
overlays = [devshell.overlays.default];
};
in
pkgs.devshell.mkShell {
packages = with pkgs; [rust rust-analyzer];
};
formatter = pkgs.alejandra;
});
}

148
src/main.rs Normal file
View File

@ -0,0 +1,148 @@
use std::env;
use std::error::Error;
use serenity::async_trait;
use serenity::builder::CreateApplicationCommands;
use serenity::model::application::command::Command;
use serenity::model::application::interaction::{Interaction, InteractionResponseType};
use serenity::model::gateway::Ready;
use serenity::model::id::GuildId;
use serenity::model::prelude::command::CommandOptionType;
use serenity::model::prelude::interaction::application_command::{
ApplicationCommandInteraction, CommandDataOptionValue,
};
use serenity::model::prelude::Role;
use serenity::prelude::*;
use tracing::{event, Level};
type StdErr<T> = Result<T, Box<dyn Error + Send + Sync>>;
struct Handler;
async fn user_to_role(ctx: &Context, cmd: &ApplicationCommandInteraction) -> StdErr<String> {
if let Some(role_option) = cmd.data.options.iter().find(|opt| opt.name == "role") {
if let Some(CommandDataOptionValue::Role(role)) = &role_option.resolved {
let guild_id = cmd.guild_id.ok_or("No guild id in command")?.into();
let user_id = cmd.user.id.into();
if cmd.data.name == "giverole" {
ctx.http
.add_member_role(
guild_id,
user_id,
role.id.into(),
Some("User added themselves to role via bot."),
)
.await?;
} else {
ctx.http
.remove_member_role(
guild_id,
user_id,
role.id.into(),
Some("User removed themselves from role via bot."),
)
.await?;
}
Ok("Role updated".into())
} else {
Err("Invalid role value".into())
}
} else {
Err("No role option given".into())
}
}
async fn handle(r: StdErr<String>) -> String {
r.unwrap_or_else(|error| {
event!(Level::ERROR, ?error, "Error handling command");
"Sorry, there was an error while handling that command.".into()
})
}
fn register_commands(cmds: &mut CreateApplicationCommands) -> &mut CreateApplicationCommands {
cmds.create_application_command(|cmd| cmd.name("ping").description("Go pong!"))
.create_application_command(|cmd| cmd.name("roles").description("List available roles"))
.create_application_command(|cmd| {
cmd.name("giverole")
.description("Add yourself to a role")
.create_option(|c| {
c.name("role")
.kind(CommandOptionType::Role)
.description("The role you want to be added to")
.required(true)
})
})
.create_application_command(|cmd| {
cmd.name("takerole")
.description("Remove yourself from a role")
.create_option(|c| {
c.name("role")
.kind(CommandOptionType::Role)
.description("The role you want to be removed from")
.required(true)
})
})
}
#[async_trait]
impl EventHandler for Handler {
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
if let Interaction::ApplicationCommand(command) = interaction {
println!("Received command interaction: {:#?}", command);
let content = match command.data.name.as_str() {
"ping" => "pong!".to_string(),
"giverole" => handle(user_to_role(&ctx, &command).await).await,
"takerole" => handle(user_to_role(&ctx, &command).await).await,
_ => "not implemented :(".to_string(),
};
if let Err(why) = command
.create_interaction_response(&ctx.http, |response| {
response
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|message| {
message.content(content).ephemeral(true)
})
})
.await
{
println!("Cannot respond to slash command: {}", why);
}
}
}
async fn ready(&self, ctx: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
// If a guild id is given, run in dev mode and register commands to that guild.
if let Ok(gid) = env::var("GUILD_ID") {
event!(Level::INFO, ?gid, "Running in debug mode for guild");
let guild_id = GuildId(gid.parse().expect("GUILD_ID must be int"));
let commands = GuildId::set_application_commands(&guild_id, &ctx.http, |commands| {
register_commands(commands)
})
.await;
} else {
let guild_command = Command::set_global_application_commands(&ctx.http, |commands| {
register_commands(commands)
})
.await;
}
}
}
#[tokio::main]
async fn main() -> StdErr<()> {
tracing_subscriber::fmt::init();
// Configure the client with your Discord bot token in the environment.
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
// Build our client.
let mut client = Client::builder(token, GatewayIntents::empty())
.event_handler(Handler)
.await
.expect("Error creating client");
Ok(client.start().await?)
}