use serenity::client::Context; use skynet_discord_bot::common::database::DataBase; use sqlx::{Pool, Sqlite}; pub(crate) mod user { use super::*; pub(crate) mod add { use super::*; use crate::commands::wolves::link::get_server_member_discord; use serde::{Deserialize, Serialize}; use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; use skynet_discord_bot::Config; use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config = config_lock.read().await; // user has to have previously linked with wolves if get_server_member_discord(&db, &command.user.id).await.is_none() { return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string(); } let sub_options = if let Some(CommandDataOption { value: CommandDataOptionValue::SubCommand(options), .. }) = command.data.options.first() { options } else { return "Please provide sub options".to_string(); }; let username = if let Some(x) = sub_options.first() { match &x.value { CommandDataOptionValue::String(username) => username.trim(), _ => return "Please provide a valid username".to_string(), } } else { return "Please provide a valid username".to_string(); }; let java = if let Some(x) = sub_options.get(1) { match &x.value { CommandDataOptionValue::Boolean(z) => !z, _ => true, } } else { true }; let username_mc; if java { // insert the username into the database match add_minecraft(&db, &command.user.id, username).await { Ok(_) => {} Err(e) => { dbg!("{:?}", e); return format!("Failure to minecraft username {:?}", username); } } username_mc = username.to_string(); } else { match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await { None => { return format!("No UID found for {:?}", username); } Some(x) => { match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { Ok(_) => {} Err(e) => { dbg!("{:?}", e); return format!("Failure to minecraft UID {:?}", &x.floodgateuid); } } username_mc = x.floodgateuid; } } } // get a list of servers that the user is a member of if let Ok(servers) = get_servers(&db, &command.user.id).await { for server in servers { whitelist_update(&vec![(username_mc.to_owned(), java)], &server.minecraft, &config.discord_token_minecraft).await; } } "Added/Updated minecraft_user info".to_string() } async fn add_minecraft(db: &Pool, user: &UserId, minecraft: &str) -> Result, Error> { sqlx::query_as::<_, Wolves>( " UPDATE wolves SET minecraft = ?2 WHERE discord = ?1; ", ) .bind(user.get() as i64) .bind(minecraft) .fetch_optional(db) .await } #[derive(Serialize, Deserialize, Debug)] struct BedrockDetails { pub gamertag: String, pub xuid: String, pub floodgateuid: String, pub icon: String, pub gamescore: String, pub accounttier: String, pub textureid: String, pub skin: String, pub linked: bool, pub java_uuid: String, pub java_name: String, } async fn get_minecraft_bedrock(username: &str, api_key: &str) -> Option { let url = format!("https://mcprofile.io/api/v1/bedrock/gamertag/{username}/"); match surf::get(url) .header("x-api-key", api_key) .header("User-Agent", "UL Computer Society") .recv_json() .await { Ok(res) => Some(res), Err(e) => { dbg!(e); None } } } async fn add_minecraft_bedrock(db: &Pool, user: &UserId, minecraft: &str) -> Result, Error> { sqlx::query_as::<_, Wolves>( " UPDATE wolves SET minecraft_uid = ?2 WHERE discord = ?1; ", ) .bind(user.get() as i64) .bind(minecraft) .fetch_optional(db) .await } async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { sqlx::query_as::<_, Minecraft>( " SELECT minecraft.* FROM minecraft JOIN ( SELECT server FROM server_members JOIN wolves USING (id_wolves) WHERE discord = ?1 ) sub on minecraft.server_discord = sub.server ", ) .bind(discord.get() as i64) .fetch_all(db) .await } } } pub(crate) mod server { use super::*; pub(crate) mod add { use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; use serenity::model::id::GuildId; use sqlx::Error; // this is to managfe the server side of commands related to minecraft use super::*; use skynet_discord_bot::common::minecraft::update_server; use skynet_discord_bot::common::minecraft::Minecraft; use skynet_discord_bot::Config; pub fn register() -> CreateCommand { CreateCommand::new("minecraft_add") .description("Add a minecraft server") .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) .add_option( CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society") .required(true), ) } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, }; let server_minecraft = if let Some(CommandDataOption { value: CommandDataOptionValue::String(id), .. }) = command.data.options.first() { id.to_string() } else { return String::from("Expected Server ID"); }; let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; let db = db_lock.read().await; match add_server(&db, &g_id, &server_minecraft).await { Ok(_) => {} Err(e) => { println!("{:?}", e); return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft); } } let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config = config_lock.read().await; update_server(&server_minecraft, &db, &g_id, &config).await; "Added/Updated minecraft_server info".to_string() } async fn add_server(db: &Pool, discord: &GuildId, minecraft: &str) -> Result, Error> { sqlx::query_as::<_, Minecraft>( " INSERT OR REPLACE INTO minecraft (server_discord, server_minecraft) VALUES (?1, ?2) ", ) .bind(discord.get() as i64) .bind(minecraft) .fetch_optional(db) .await } } pub(crate) mod list { use serenity::all::CommandInteraction; use serenity::builder::CreateCommand; use serenity::client::Context; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; use skynet_discord_bot::Config; pub fn register() -> CreateCommand { CreateCommand::new("minecraft_list") .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) .description("List your minecraft servers") } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, }; let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; let db = db_lock.read().await; let servers = get_minecraft_config_server(&db, g_id).await; if servers.is_empty() { return "No minecraft servers, use /minecraft_add to add one".to_string(); } let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config = config_lock.read().await; let mut result = "Server Information:\n".to_string(); for server in get_minecraft_config_server(&db, g_id).await { if let Some(x) = server_information(&server.minecraft, &config.discord_token_minecraft).await { result.push_str(&format!( r#" Name: {name} ID: {id} Online: {online} Info: {description} Link: "#, name = &x.attributes.name, online = !x.attributes.is_suspended, description = &x.attributes.description, id = &x.attributes.identifier )); } } result.to_string() } } pub(crate) mod delete { use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; use serenity::builder::CreateCommand; use serenity::client::Context; use serenity::model::id::GuildId; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::Minecraft; use sqlx::{Error, Pool, Sqlite}; pub fn register() -> CreateCommand { CreateCommand::new("minecraft_delete") .description("Delete a minecraft server") .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) .add_option( CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society") .required(true), ) } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, }; let server_minecraft = if let Some(CommandDataOption { value: CommandDataOptionValue::String(id), .. }) = command.data.options.first() { id.to_string() } else { return String::from("Expected Server ID"); }; let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; let db = db_lock.read().await; match server_remove(&db, &g_id, &server_minecraft).await { Ok(_) => {} Err(e) => { println!("{:?}", e); return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft); } } // no need to clear teh whitelist as it will be reset within 24hr anyways "Removed minecraft_server info".to_string() } async fn server_remove(db: &Pool, discord: &GuildId, minecraft: &str) -> Result, Error> { sqlx::query_as::<_, Minecraft>( " DELETE FROM minecraft WHERE server_discord = ?1 AND server_minecraft = ?2 ", ) .bind(discord.get() as i64) .bind(minecraft) .fetch_optional(db) .await } } }