Slash commands are cool
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

148 line
6.3KB

  1. #![feature(proc_macro_hygiene, decl_macro)]
  2. mod auth_tools;
  3. mod discord;
  4. mod command_definitions;
  5. mod gitea;
  6. mod gitea_command;
  7. mod macros;
  8. #[macro_use] extern crate rocket;
  9. use lazy_static::lazy_static;
  10. use rocket_contrib::json::Json;
  11. use std::sync::RwLock;
  12. use crate::auth_tools::{AuthenticatedInteraction};
  13. use crate::discord::{Interaction, InteractionResponse, InteractionResponseRaw, InteractionApplicationCommandCallbackData};
  14. use crate::command_definitions::{hello_world, def_hello_world, def_gitea_release, def_gitea_issue, def_cardlife, def_robocraft, def_techblox};
  15. use crate::gitea_command::{gitea_release, gitea_issue};
  16. use crate::macros::{cardlife, robocraft, techblox};
  17. static GLOBAL_COMMAND_KEY: &str = "GLOBAL command KEY";
  18. lazy_static! {
  19. pub static ref VERIFICATION_KEY: RwLock<Option<ed25519_dalek::PublicKey>> = RwLock::new(None);
  20. pub static ref APPLICATION_ID: RwLock<Option<String>> = RwLock::new(None);
  21. pub static ref BOT_TOKEN: RwLock<Option<String>> = RwLock::new(None);
  22. }
  23. #[post("/", data = "<interaction>")]
  24. fn root_post(interaction: AuthenticatedInteraction) -> Json<InteractionResponseRaw> {
  25. Json(
  26. match &interaction.interaction {
  27. Interaction::Ping {} => InteractionResponse::Pong {},
  28. Interaction::Command {data, ..} => {
  29. match &data.name as &str {
  30. "hello-world" => hello_world(&interaction.interaction),
  31. "gitea-release" => gitea_release(&interaction.interaction),
  32. "gitea-issue" => gitea_issue(&interaction.interaction),
  33. "cardlife" => cardlife(&interaction.interaction),
  34. "robocraft" => robocraft(&interaction.interaction),
  35. "techblox" => techblox(&interaction.interaction),
  36. _ => InteractionResponse::ChannelMessageWithSource {
  37. data: Some(InteractionApplicationCommandCallbackData {
  38. tts: false,
  39. content: "Oops, that's not implemented yet!".to_string(),
  40. embeds: None,
  41. allowed_mentions: None,
  42. flags: Some(64),
  43. })
  44. },
  45. }
  46. },
  47. _ => InteractionResponse::AcknowledgeWithSource {},
  48. }.raw()
  49. )
  50. }
  51. #[get("/")]
  52. fn hello_get() -> &'static str {
  53. "Hello, why are you here?"
  54. }
  55. fn main() {
  56. // Init verification key
  57. let public_key = std::env::var("DISCORD_PUBLIC_KEY")
  58. .expect("Environment variable DISCORD_PUBLIC_KEY not found");
  59. //println!("Discord Pub Key {}", &public_key);
  60. *VERIFICATION_KEY.write().unwrap() = Some(ed25519_dalek::PublicKey::from_bytes(
  61. &hex::decode(public_key)
  62. .expect("Invalid hex string")).expect("Invalid public key"));
  63. // get application ID for sending API requests
  64. let app_id = std::env::var("DISCORD_APP_ID")
  65. .expect("Environment variable DISCORD_APP_ID not found");
  66. //println!("Discord App Id {}", &app_id);
  67. *APPLICATION_ID.write().unwrap() = Some(app_id.to_string());
  68. // get bot token for sending API requests
  69. let token = std::env::var("DISCORD_TOKEN")
  70. .expect("Environment variable DISCORD_TOKEN not found");
  71. //println!("Discord Token {}", &token);
  72. *BOT_TOKEN.write().unwrap() = Some(token.to_string());
  73. // send API requests to bootstrap commands
  74. let req_client = reqwest::blocking::Client::new();
  75. // TODO add more commands
  76. register_command(&def_hello_world, &req_client);
  77. register_command(&def_gitea_release, &req_client);
  78. register_command(&def_gitea_issue, &req_client);
  79. register_command(&def_cardlife, &req_client);
  80. register_command(&def_robocraft, &req_client);
  81. register_command(&def_techblox, &req_client);
  82. // start web server
  83. rocket::ignite().mount("/", routes![root_post, hello_get]).launch();
  84. }
  85. fn register_command(f: &dyn Fn() -> (discord::ApplicationCommand, Option<String>),
  86. client: &reqwest::blocking::Client) {
  87. let (payload, guild_opt) = f();
  88. if let Some(guild_id) = guild_opt {
  89. // create/upsert command
  90. let url = format!("https://discord.com/api/v8/applications/{}/guilds/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone(), &guild_id);
  91. let res = client.post(&url)
  92. .header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
  93. .json(&payload.raw())
  94. .send();
  95. if let Ok(d) = res {
  96. println!("Registered/updated `{}` ({}) GUILD:{}", &payload.name, &d.status().as_str(), &guild_id);
  97. //println!("{}", &d.text().unwrap());
  98. }
  99. } else {
  100. // create/upsert command
  101. let url = format!("https://discord.com/api/v8/applications/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone());
  102. let res = client.post(&url)
  103. .header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
  104. .json(&payload.raw())
  105. .send();
  106. if let Ok(d) = res {
  107. println!("Registered/updated `{}` ({})", &payload.name, &d.status().as_str());
  108. //println!("{}", &d.text().unwrap());
  109. }
  110. }
  111. }
  112. /*
  113. fn get_commands(client: &reqwest::blocking::Client) -> Vec<ApplicationCommand> {
  114. let url = format!("https://discord.com/api/v8/applications/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone());
  115. let res = client.get(&url)
  116. .header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
  117. .send();
  118. if let Ok(d) = res {
  119. println!("Commands GET status {}", &d.status().as_str());
  120. return d.json().unwrap();
  121. }
  122. return Vec::new();
  123. }
  124. fn get_guild_commands(client: &reqwest::blocking::Client, guild_id: &str) -> Vec<ApplicationCommand> {
  125. let url = format!("https://discord.com/api/v8/applications/{}/guilds/{}/commands", APPLICATION_ID.read().unwrap().as_ref().unwrap().clone(), guild_id);
  126. let res = client.get(&url)
  127. .header("Authorization", format!("Bot {}", BOT_TOKEN.read().unwrap().as_ref().unwrap().clone()))
  128. .send();
  129. if let Ok(d) = res {
  130. println!("Commands GET status {}", &d.status().as_str());
  131. return d.json().unwrap();
  132. }
  133. return Vec::new();
  134. }
  135. */