Browse Source

Create response framework

master
NGnius (Graham) 3 years ago
parent
commit
6c476e2f98
5 changed files with 149 additions and 78 deletions
  1. +2
    -6
      Cargo.toml
  2. +4
    -1
      src/auth_tools.rs
  3. +16
    -2
      src/command_definitions.rs
  4. +90
    -7
      src/discord.rs
  5. +37
    -62
      src/main.rs

+ 2
- 6
Cargo.toml View File

@@ -8,14 +8,10 @@ edition = "2018"

[dependencies]
rocket = "0.4.7"
rocket_contrib = { version = "0.4.7", default-features = false, features = ["json"]}
reqwest = { version = "0.11.1", features = ["json", "blocking"]}
ed25519-dalek = "1.0.1"
lazy_static = "1.4.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0" }
hex = "0.4.2"
# futures = "0.3.12"

[dependencies.rocket_contrib]
default-features = false
features = ["json"]
hex = "0.4.2"

+ 4
- 1
src/auth_tools.rs View File

@@ -56,9 +56,12 @@ impl FromDataSimple for AuthenticatedInteraction {
timestamp: timestamp.clone(),
interaction: payload.interaction(),
};
if auth.interaction.is_invalid() {
return Outcome::Failure((Status::BadRequest, ()));
}
return Outcome::Success(auth);
}
println!("Invalid json payload {}\n({})", &string_buf[..string_len], serde_json::from_str::<Interaction>(&string_buf[..string_len]).err().unwrap());
println!("Invalid json payload {}\n({})", &string_buf[..string_len], serde_json::from_str::<InteractionRaw>(&string_buf[..string_len]).err().unwrap());
return Outcome::Failure((Status::Unauthorized, ()))
}
println!("Invalid body");


+ 16
- 2
src/command_definitions.rs View File

@@ -1,13 +1,27 @@
use crate::discord;
use crate::discord::{Interaction, InteractionResponse, InteractionApplicationCommandCallbackData};

// fn foo() -> (definition, private?)

pub fn hello_world() -> (discord::ApplicationCommand, Option<String>) {
// hello-world data for telling Discord about it
pub fn def_hello_world() -> (discord::ApplicationCommand, Option<String>) {
(discord::ApplicationCommand {
id: None,
application_id: None,
name: "hello-world".to_string(),
description: "Hello World".to_string(),
options: None,
}, None)//Some("616329232389505055".to_string()))
}, Some("616329232389505055".to_string()))
}

// hello-world action when someone uses the command on Discord
pub fn hello_world(interaction: &Interaction) -> InteractionResponse {
let cmd = interaction.cmd().unwrap();
InteractionResponse::ChannelMessage {
data: Some(InteractionApplicationCommandCallbackData {
tts: false,
content: format!("Hello {}!", cmd.member.nick.unwrap_or(cmd.member.user.unwrap().username)),
allowed_mentions: None,
}),
}
}

+ 90
- 7
src/discord.rs View File

@@ -5,9 +5,12 @@ pub struct InteractionRaw {
#[serde(rename = "type")]
pub type_: usize,
pub token: Option<String>,
pub member: Option<GuildMember>,
pub id: Option<String>,
pub guild_id: Option<String>,
pub data: Option<ApplicationCommandInteractionData>,
pub channel_id: Option<String>,
pub version: Option<usize>,
}

impl InteractionRaw {
@@ -16,29 +19,30 @@ impl InteractionRaw {
1 => Interaction::Ping {},
2 => Interaction::Command {
token: self.token.clone().unwrap(),
member: self.member.clone().unwrap(),
id: self.id.clone().unwrap(),
guild_id: self.guild_id.clone().unwrap(),
data: self.data.clone().unwrap(),
channel_id: self.channel_id.clone().unwrap(),
version: self.version.unwrap_or(1),
},
_ => Interaction::Invalid {},
}
}
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "type")]
pub enum Interaction {
#[serde(rename = "1")]
//#[serde(rename = "1")]
Ping {},
#[serde(rename = "2")]
//#[serde(rename = "2")]
Command {
token: String,
// member: Member,
member: GuildMember,
id: String,
guild_id: String,
// data: Command,
data: ApplicationCommandInteractionData,
channel_id: String,
//version: usize,
version: usize,
},
Invalid {},
}
@@ -51,6 +55,85 @@ impl Interaction {
Self::Invalid {..} => false,
}
}

pub fn is_command(&self) -> bool {
match self {
Self::Ping {..} => false,
Self::Command {..} => true,
Self::Invalid {..} => false,
}
}

pub fn is_invalid(&self) -> bool {
match self {
Self::Ping {..} => false,
Self::Command {..} => false,
Self::Invalid {..} => true,
}
}

pub fn cmd(&self) -> Option<InteractionCommand> {
match self {
Self::Ping {..} => None,
Self::Command { token, member, id, guild_id, data, channel_id, version } => Some(InteractionCommand {
token: token.to_string(),
member: member.clone(),
id: id.to_string(),
guild_id: guild_id.to_string(),
data: data.clone(),
channel_id: channel_id.to_string(),
version: *version,
}),
Self::Invalid {..} => None,
}
}
}

pub struct InteractionCommand {
pub token: String,
pub member: GuildMember,
pub id: String,
pub guild_id: String,
pub data: ApplicationCommandInteractionData,
pub channel_id: String,
pub version: usize,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct GuildMember {
pub user: Option<User>,
pub nick: Option<String>,
pub role: Vec<String>,
pub joined_at: String,
pub premium_since: Option<String>,
pub deaf: bool,
pub mute: bool,
pub pending: Option<bool>,
pub permissions: Option<String>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct User {
pub id: String,
pub username: String,
pub discriminator: String,
pub avatar: Option<String>,
pub bot: Option<bool>,
pub system: Option<bool>,
pub mfa_enabled: Option<bool>,
pub locale: Option<String>,
pub verified: Option<String>,
pub email: Option<String>,
pub flags: Option<usize>,
pub premium_type: Option<usize>,
pub public_flags: Option<usize>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct ApplicationCommandInteractionData {
pub id: String,
pub name: String,
pub options: Option<Vec<ApplicationCommandInteractionData>>,
}

pub enum InteractionResponse {


+ 37
- 62
src/main.rs View File

@@ -10,9 +10,8 @@ use lazy_static::lazy_static;
use rocket_contrib::json::Json;
use std::sync::RwLock;
use crate::auth_tools::{AuthenticatedInteraction};
use crate::discord::{InteractionResponse, InteractionApplicationCommandCallbackData, ApplicationCommand, InteractionResponseRaw};
use crate::command_definitions::hello_world;
use std::collections::{HashMap, HashSet};
use crate::discord::{Interaction, InteractionResponse, /*ApplicationCommand, */InteractionResponseRaw};
use crate::command_definitions::{hello_world, def_hello_world};

static GLOBAL_COMMAND_KEY: &str = "GLOBAL command KEY";

@@ -24,17 +23,18 @@ lazy_static! {

#[post("/", data = "<interaction>")]
fn root_post(interaction: AuthenticatedInteraction) -> Json<InteractionResponseRaw> {
if interaction.interaction.is_ping() {
return Json(InteractionResponse::Pong{}.raw())
}
let resp = InteractionResponse::ChannelMessage {
data: Some(InteractionApplicationCommandCallbackData {
tts: false,
content: "Hello".to_string(),
allowed_mentions: None,
}),
};
Json(resp.raw())
Json(
match &interaction.interaction {
Interaction::Ping {} => InteractionResponse::Pong {},
Interaction::Command {data, ..} => {
match &data.name as &str {
"hello-world" => hello_world(&interaction.interaction),
_ => InteractionResponse::AcknowledgeWithSource {},
}
},
_ => InteractionResponse::AcknowledgeWithSource {},
}.raw()
)
}

#[get("/")]
@@ -63,67 +63,41 @@ fn main() {

// send API requests to bootstrap commands
let req_client = reqwest::blocking::Client::new();
let mut seen_cmds = HashMap::<String, HashSet<String>>::new();
// TODO
register_command(&hello_world, &req_client, &mut seen_cmds);
// TODO add more commands
register_command(&def_hello_world, &req_client);
// start web server
rocket::ignite().mount("/", routes![root_post, hello_get]).launch();
}

fn register_command(f: &dyn Fn() -> (discord::ApplicationCommand, Option<String>),
client: &reqwest::blocking::Client,
registered: &mut HashMap<String, HashSet<String>>) {
client: &reqwest::blocking::Client) {
let (payload, guild_opt) = f();
if let Some(guild_id) = guild_opt {
if !registered.contains_key(&guild_id) {
let mut seen_guild_cmds = HashSet::new();
let cmds = get_guild_commands(client, &guild_id);
for c in cmds {
println!("Found app command {} ID:{} App:{}", &c.name, &c.id.unwrap(), guild_id);
seen_guild_cmds.insert(c.name);
}
registered.insert(guild_id.clone(), seen_guild_cmds);
}
if !registered.get(&guild_id).unwrap().contains(&payload.name) {
// create new command
let url = format!("https://discord.com/api/v8/applications/{}/guilds/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone(), &guild_id);
let res = client.post(&url)
.header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
.json(&payload)
.send();
if let Ok(d) = res {
println!("`{}` status {}", &payload.name, &d.status().as_str());
println!("{}", &d.text().unwrap());
registered.get_mut(&guild_id).unwrap().insert(payload.name);
}
// create/upsert command
let url = format!("https://discord.com/api/v8/applications/{}/guilds/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone(), &guild_id);
let res = client.post(&url)
.header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
.json(&payload)
.send();
if let Ok(d) = res {
println!("`{}` status {}", &payload.name, &d.status().as_str());
println!("{}", &d.text().unwrap());
}
} else {
if !registered.contains_key(GLOBAL_COMMAND_KEY) {
let mut seen_cmds = HashSet::new();
let cmds = get_commands(client);
for c in cmds {
println!("Found global command {} ID:{}", &c.name, &c.id.unwrap());
seen_cmds.insert(c.name);
}
registered.insert(GLOBAL_COMMAND_KEY.to_string(), seen_cmds);
// create/upsert command
let url = format!("https://discord.com/api/v8/applications/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone());
let res = client.post(&url)
.header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
.json(&payload)
.send();
if let Ok(d) = res {
println!("`{}` status {}", &payload.name, &d.status().as_str());
println!("{}", &d.text().unwrap());
}
if !registered.get(GLOBAL_COMMAND_KEY).unwrap().contains(&payload.name) {
// create new command
let url = format!("https://discord.com/api/v8/applications/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone());
let res = client.post(&url)
.header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
.json(&payload)
.send();
if let Ok(d) = res {
println!("`{}` status {}", &payload.name, &d.status().as_str());
println!("{}", &d.text().unwrap());
registered.get_mut(GLOBAL_COMMAND_KEY.clone()).unwrap().insert(payload.name);
}
}

}
}

/*
fn get_commands(client: &reqwest::blocking::Client) -> Vec<ApplicationCommand> {
let url = format!("https://discord.com/api/v8/applications/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone());
let res = client.get(&url)
@@ -147,3 +121,4 @@ fn get_guild_commands(client: &reqwest::blocking::Client, guild_id: &str) -> Vec
}
return Vec::new();
}
*/

Loading…
Cancel
Save