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.

137 lines
5.6KB

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