verification stat checker
This commit is contained in:
parent
7886c6b46d
commit
ce999bd271
8 changed files with 19899 additions and 185 deletions
19524
api_examples/player.json
Normal file
19524
api_examples/player.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,16 +1,15 @@
|
||||||
use poise::CreateReply;
|
use poise::{ChoiceParameter, CreateReply};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serenity::all::ButtonStyle;
|
|
||||||
use serenity::all::{
|
use serenity::all::{
|
||||||
ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateMessage, ReactionType,
|
ButtonStyle, ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateEmbed,
|
||||||
User,
|
CreateMessage, ReactionType, User,
|
||||||
};
|
};
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
use crate::commands::command_helper::cooldown;
|
use crate::commands::command_helper::cooldown;
|
||||||
use crate::data::account_links::{Link, LinkId, Uuid};
|
use crate::data::account_links::{link, Link, Uuid};
|
||||||
|
use crate::data::hypixel::Arcade;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::error::Error::Other;
|
|
||||||
use crate::Caches;
|
use crate::Caches;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
|
@ -25,6 +24,16 @@ pub(crate) async fn account(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(ChoiceParameter, PartialEq, Eq)]
|
||||||
|
enum Mode {
|
||||||
|
#[name = "normal"]
|
||||||
|
Normal,
|
||||||
|
#[name = "forced"]
|
||||||
|
Forced,
|
||||||
|
#[name = "forced + no hypixel api (instant accept)"]
|
||||||
|
NoApi,
|
||||||
|
}
|
||||||
|
|
||||||
#[poise::command(slash_command, ephemeral = "false")]
|
#[poise::command(slash_command, ephemeral = "false")]
|
||||||
/// Verify a Minecraft account on the Zombies MultiPlayer Discord.
|
/// Verify a Minecraft account on the Zombies MultiPlayer Discord.
|
||||||
pub(crate) async fn add(
|
pub(crate) async fn add(
|
||||||
|
@ -33,120 +42,74 @@ pub(crate) async fn add(
|
||||||
#[min_length = 2]
|
#[min_length = 2]
|
||||||
#[max_length = 16]
|
#[max_length = 16]
|
||||||
ign: String,
|
ign: String,
|
||||||
#[description = "Discord User"] user: Option<User>,
|
#[description = "Discord User"] target: Option<User>,
|
||||||
#[description = "admin-only"] force: Option<bool>,
|
#[description = "ZMP-admin only"] mode: Option<Mode>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
ctx.defer().await?;
|
ctx.defer().await?;
|
||||||
let force: bool =
|
let mode = if ctx.framework().options().owners.contains(&ctx.author().id) {
|
||||||
force.unwrap_or(false) && ctx.framework().options.owners.contains(&ctx.author().id) && {
|
mode.unwrap_or(Mode::Normal)
|
||||||
let _ = user.as_ref().ok_or(Other(
|
} else {
|
||||||
"Warning: attempted to run forced account add without specifying a target Discord \
|
Mode::Normal
|
||||||
account."
|
|
||||||
.to_string(),
|
|
||||||
))?;
|
|
||||||
true
|
|
||||||
};
|
};
|
||||||
let user: User = user.unwrap_or(ctx.author().clone());
|
let user: User = target.unwrap_or(if mode == Mode::Normal {
|
||||||
|
ctx.author().clone()
|
||||||
|
} else {
|
||||||
|
return Err(Error::Other(
|
||||||
|
"Force mode cannot be ran without specifying a different Discord account other than \
|
||||||
|
your own.".to_string(),
|
||||||
|
));
|
||||||
|
});
|
||||||
let uuid: Uuid = Uuid::for_ign(
|
let uuid: Uuid = Uuid::for_ign(
|
||||||
ign.as_str(),
|
ign.as_str(),
|
||||||
&ctx.data().clients.general,
|
&ctx.data().clients.general,
|
||||||
&ctx.data().caches,
|
&ctx.data().caches,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
match force
|
let link_status = match mode {
|
||||||
|| uuid
|
Mode::Normal => {
|
||||||
.has_discord_user(&user, &ctx.data().clients.hypixel_api_client)
|
let profile = uuid
|
||||||
|
.hypixel_player_data(&ctx.data().clients.hypixel_api_client)
|
||||||
.await?
|
.await?
|
||||||
{
|
.map_if_discord(user.name.as_str())?;
|
||||||
true => {
|
let done = link(&ctx.data().sqlite_pool, uuid, &user).await?;
|
||||||
let pool = &ctx.data().sqlite_pool;
|
ChannelId::new(1257776992497959075_u64)
|
||||||
let status: &str = match LinkId::try_from_minecraft(pool, uuid.as_str()).await {
|
|
||||||
Err(_) => match LinkId::try_from_discord(pool, user.id.get()).await {
|
|
||||||
Err(_) => {
|
|
||||||
let id = LinkId::new(pool).await?;
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"INSERT INTO discord_links VALUES ({id}, {});",
|
|
||||||
user.id.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"INSERT INTO minecraft_links VALUES ({id}, \"{}\");",
|
|
||||||
uuid.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Linked your Discord and Minecraft account."
|
|
||||||
}
|
|
||||||
Ok(dc_id) => {
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"INSERT INTO minecraft_links VALUES ({dc_id}, \"{}\");",
|
|
||||||
uuid.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Your Discord account has previously had an account linked. Added the new \
|
|
||||||
link."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ok(mc_id) => match LinkId::try_from_discord(pool, user.id.get()).await {
|
|
||||||
Err(_) => {
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"INSERT INTO discord_links VALUES ({mc_id}, {});",
|
|
||||||
user.id.get()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Your Minecraft account has previously had an account linked. Added the \
|
|
||||||
new link."
|
|
||||||
}
|
|
||||||
Ok(dc_id) => {
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"UPDATE minecraft_links SET link_id = {mc_id} WHERE link_id = {dc_id};",
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
sqlx::query(
|
|
||||||
format!(
|
|
||||||
"UPDATE discord_links SET link_id = {mc_id} WHERE link_id = {dc_id};",
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
"Both your Discord and Minecraft account had linked accounts. Merged all \
|
|
||||||
account links."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let s = format!(
|
|
||||||
"Verification request for <@{}> with IGN `{}`",
|
|
||||||
user.id.get(),
|
|
||||||
ign
|
|
||||||
);
|
|
||||||
ChannelId::new(1257776992497959075)
|
|
||||||
.send_message(
|
.send_message(
|
||||||
ctx,
|
ctx,
|
||||||
CreateMessage::new()
|
create_verification_message(profile.arcade_stats().copied().unwrap_or_default(), &user, ign)
|
||||||
.content(s)
|
|
||||||
.allowed_mentions(
|
|
||||||
CreateAllowedMentions::new().empty_roles().all_users(true),
|
|
||||||
)
|
)
|
||||||
|
.await?;
|
||||||
|
done
|
||||||
|
}
|
||||||
|
Mode::Forced => {
|
||||||
|
let profile = uuid
|
||||||
|
.hypixel_player_data(&ctx.data().clients.hypixel_api_client)
|
||||||
|
.await?;
|
||||||
|
let done = link(&ctx.data().sqlite_pool, uuid, &user).await?;
|
||||||
|
ChannelId::new(1257776992497959075_u64)
|
||||||
|
.send_message(
|
||||||
|
ctx,
|
||||||
|
create_verification_message(profile.arcade_stats().copied().unwrap_or_default(), &user, ign)
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
done
|
||||||
|
}
|
||||||
|
Mode::NoApi => {
|
||||||
|
let done = link(&ctx.data().sqlite_pool, uuid, &user).await?;
|
||||||
|
done
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ctx.send(CreateReply::default().content(link_status))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_verification_message(stats: Arcade, user: &User, ign: String) -> CreateMessage {
|
||||||
|
let embed = CreateEmbed::new()
|
||||||
|
.fields(Vec::<(&'static str, String, bool)>::from(stats))
|
||||||
|
.title(format!("Verification request for {user} `{ign}`"));
|
||||||
|
CreateMessage::new()
|
||||||
|
.embed(embed)
|
||||||
|
.allowed_mentions(CreateAllowedMentions::new().empty_roles().all_users(true))
|
||||||
.components(vec![CreateActionRow::Buttons(vec![
|
.components(vec![CreateActionRow::Buttons(vec![
|
||||||
CreateButton::new("accept_verification")
|
CreateButton::new("accept_verification")
|
||||||
.emoji(ReactionType::from('✅'))
|
.emoji(ReactionType::from('✅'))
|
||||||
|
@ -157,18 +120,7 @@ pub(crate) async fn add(
|
||||||
CreateButton::new("list_accounts")
|
CreateButton::new("list_accounts")
|
||||||
.emoji(ReactionType::from('📜'))
|
.emoji(ReactionType::from('📜'))
|
||||||
.style(ButtonStyle::Primary),
|
.style(ButtonStyle::Primary),
|
||||||
])]),
|
])])
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
ctx.send(CreateReply::default().content(status)).await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
false => Err(Error::Other(format!(
|
|
||||||
"The Discord account linked on Hypixel does not match the specified discord \
|
|
||||||
account.\nPlease set your linked Discord account on Hypixel to `{}`.",
|
|
||||||
user.name
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::data::account_links::{Link, Uuid};
|
||||||
use crate::data::helpstart_api::fetch_all;
|
use crate::data::helpstart_api::fetch_all;
|
||||||
use crate::data::helpstart_api::ListType::*;
|
use crate::data::helpstart_api::ListType::*;
|
||||||
use crate::data::mojang::name;
|
use crate::data::mojang::name;
|
||||||
|
@ -12,7 +13,6 @@ use poise::serenity_prelude::CreateInteractionResponse;
|
||||||
use poise::serenity_prelude::CreateInteractionResponseMessage;
|
use poise::serenity_prelude::CreateInteractionResponseMessage;
|
||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
use crate::data::account_links::{Link, Uuid};
|
|
||||||
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
|
@ -26,9 +26,11 @@ pub(crate) async fn helpstart(ctx: Context<'_>, user: Option<String>) -> Result<
|
||||||
let mc_accounts = match user {
|
let mc_accounts = match user {
|
||||||
None => {
|
None => {
|
||||||
futures::future::try_join_all(
|
futures::future::try_join_all(
|
||||||
Vec::<Uuid>::from(Link::try_from_discord(ctx.author(), &ctx.data().sqlite_pool).await?)
|
Vec::<Uuid>::from(
|
||||||
|
Link::try_from_discord(ctx.author(), &ctx.data().sqlite_pool).await?,
|
||||||
|
)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.get()))
|
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.uuid))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
@ -39,9 +41,19 @@ pub(crate) async fn helpstart(ctx: Context<'_>, user: Option<String>) -> Result<
|
||||||
let bots = fetch_all(&ctx.data().clients.local_api_client).await?;
|
let bots = fetch_all(&ctx.data().clients.local_api_client).await?;
|
||||||
let usable = bots
|
let usable = bots
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|b| match b.list_type() {
|
.filter(|bot| match bot.list_type() {
|
||||||
Whitelist => b.list().iter().any(|w| mc_accounts.contains(w)),
|
Whitelist => bot.list().iter().map(|wl| wl.to_lowercase()).any(|wl| {
|
||||||
Blacklist => mc_accounts.iter().any(|m| !b.list().contains(m)),
|
mc_accounts
|
||||||
|
.iter()
|
||||||
|
.map(|acc| acc.to_lowercase())
|
||||||
|
.any(|acc| acc == wl)
|
||||||
|
}),
|
||||||
|
Blacklist => mc_accounts.iter().map(|acc| acc.to_lowercase()).any(|acc| {
|
||||||
|
bot.list()
|
||||||
|
.iter()
|
||||||
|
.map(|bl| bl.to_lowercase())
|
||||||
|
.all(|b| b != acc)
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub enum Difficulty {
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
install_context = "Guild",
|
install_context = "Guild",
|
||||||
interaction_context = "Guild",
|
interaction_context = "Guild"
|
||||||
)]
|
)]
|
||||||
/// Find a team for Hypixel Zombies.
|
/// Find a team for Hypixel Zombies.
|
||||||
pub(crate) async fn lfg(
|
pub(crate) async fn lfg(
|
||||||
|
@ -103,7 +103,10 @@ pub(crate) async fn lfg(
|
||||||
AlienArcadium => Normal,
|
AlienArcadium => Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut reply_content: String = format!("-# LFG by {}\n## <@&{ping}> {current}/{desired} {map_name}", ctx.author());
|
let mut reply_content: String = format!(
|
||||||
|
"-# LFG by {}\n## <@&{ping}> {current}/{desired} {map_name}",
|
||||||
|
ctx.author()
|
||||||
|
);
|
||||||
match difficulty {
|
match difficulty {
|
||||||
Normal => {}
|
Normal => {}
|
||||||
Difficulty::Hard | Difficulty::Rip => {
|
Difficulty::Hard | Difficulty::Rip => {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use super::hypixel::HypixelResponse;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use crate::data::hypixel::HypixelPlayer;
|
||||||
use crate::Caches;
|
use crate::Caches;
|
||||||
use crate::Error::{self, *};
|
use crate::Error;
|
||||||
use poise::serenity_prelude::User;
|
use poise::serenity_prelude::User;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use sqlx::query_as;
|
use sqlx::query_as;
|
||||||
|
@ -9,14 +11,16 @@ use sqlx::Sqlite;
|
||||||
|
|
||||||
#[derive(PartialEq, sqlx::FromRow)]
|
#[derive(PartialEq, sqlx::FromRow)]
|
||||||
pub(crate) struct Uuid {
|
pub(crate) struct Uuid {
|
||||||
uuid: String,
|
pub uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Uuid {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.uuid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Uuid {
|
impl Uuid {
|
||||||
pub(crate) fn get(self) -> String {
|
|
||||||
self.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_str(&self) -> &str {
|
pub(crate) fn as_str(&self) -> &str {
|
||||||
self.uuid.as_str()
|
self.uuid.as_str()
|
||||||
}
|
}
|
||||||
|
@ -30,21 +34,13 @@ impl Uuid {
|
||||||
let ign = crate::data::mojang::name(c, cli, self.uuid.clone()).await?;
|
let ign = crate::data::mojang::name(c, cli, self.uuid.clone()).await?;
|
||||||
Ok(ign)
|
Ok(ign)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Uuid {
|
pub(crate) async fn hypixel_player_data(
|
||||||
pub(crate) async fn has_discord_user(
|
|
||||||
&self,
|
&self,
|
||||||
user: &User,
|
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> Result<bool, Error> {
|
) -> Result<HypixelPlayer, Error> {
|
||||||
let res = HypixelResponse::get(self.uuid.as_str(), client).await?;
|
let p = HypixelPlayer::get(self.uuid.as_str(), client).await?;
|
||||||
let matches = res.discord().ok_or(Other(
|
Ok(p)
|
||||||
"The Hypixel profile has no Discord account linked. Please follow the steps in \
|
|
||||||
<#1256219552568840263>"
|
|
||||||
.to_string(),
|
|
||||||
))? == user.name;
|
|
||||||
Ok(matches)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,7 +182,9 @@ impl LinkId {
|
||||||
)
|
)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(Other("This user has no accounts linked.".to_string()))
|
.ok_or(Error::Other(
|
||||||
|
"This user has no accounts linked.".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn try_from_minecraft(
|
pub(crate) async fn try_from_minecraft(
|
||||||
|
@ -201,6 +199,79 @@ impl LinkId {
|
||||||
)
|
)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(Other("This player has no accounts linked.".to_string()))
|
.ok_or(Error::Other("This player has no accounts linked.".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn link(pool: &Pool<Sqlite>, uuid: Uuid, user: &User) -> Result<&'static str, Error> {
|
||||||
|
match LinkId::try_from_minecraft(pool, uuid.as_str()).await {
|
||||||
|
Err(_) => match LinkId::try_from_discord(pool, user.id.get()).await {
|
||||||
|
Err(_) => {
|
||||||
|
let id = LinkId::new(pool).await?;
|
||||||
|
sqlx::query(
|
||||||
|
format!(
|
||||||
|
"INSERT INTO discord_links VALUES ({id}, {});",
|
||||||
|
user.id.get()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
sqlx::query(
|
||||||
|
format!("INSERT INTO minecraft_links VALUES ({id}, \"{uuid}\");",).as_str(),
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok("Linked your Discord and Minecraft account.")
|
||||||
|
}
|
||||||
|
Ok(dc_id) => {
|
||||||
|
sqlx::query(
|
||||||
|
format!("INSERT INTO minecraft_links VALUES ({dc_id}, \"{uuid}\");",).as_str(),
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(
|
||||||
|
"Your Discord account has previously had an account linked. Added the new \
|
||||||
|
link.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(mc_id) => match LinkId::try_from_discord(pool, user.id.get()).await {
|
||||||
|
Err(_) => {
|
||||||
|
sqlx::query(
|
||||||
|
format!(
|
||||||
|
"INSERT INTO discord_links VALUES ({mc_id}, {});",
|
||||||
|
user.id.get()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(
|
||||||
|
"Your Minecraft account has previously had an account linked. Added the new \
|
||||||
|
link.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(dc_id) => {
|
||||||
|
sqlx::query(
|
||||||
|
format!(
|
||||||
|
"UPDATE minecraft_links SET link_id = {mc_id} WHERE link_id = {dc_id};",
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
sqlx::query(
|
||||||
|
format!("UPDATE discord_links SET link_id = {mc_id} WHERE link_id = {dc_id};",)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(
|
||||||
|
"Both your Discord and Minecraft account had linked accounts. Merged all \
|
||||||
|
account links.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,164 @@
|
||||||
use crate::Error;
|
use crate::{error::LinkingError, Error};
|
||||||
|
use getset::{CopyGetters, Getters};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Getters)]
|
||||||
|
#[getset(get)]
|
||||||
struct Links {
|
struct Links {
|
||||||
#[serde(rename = "DISCORD")]
|
#[serde(rename = "DISCORD")]
|
||||||
discord: Option<String>,
|
discord: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, Getters)]
|
||||||
|
#[getset(get)]
|
||||||
struct SocialMedia {
|
struct SocialMedia {
|
||||||
links: Option<Links>,
|
links: Option<Links>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, CopyGetters, Clone, Copy)]
|
||||||
struct HypixelPlayer {
|
#[getset(get_copy)]
|
||||||
|
pub struct Arcade {
|
||||||
|
#[serde(rename = "wins_zombies")]
|
||||||
|
wins: Option<u16>, //one day slies will overflow this
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_deadend_normal")]
|
||||||
|
de_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_deadend_hard")]
|
||||||
|
deh_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_deadend_rip")]
|
||||||
|
der_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_badblood_normal")]
|
||||||
|
bb_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_badblood_hard")]
|
||||||
|
bbh_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_badblood_rip")]
|
||||||
|
bbr_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_alienarcadium_normal")]
|
||||||
|
aa_win: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_prison_normal")]
|
||||||
|
p_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_prison_hard")]
|
||||||
|
ph_r30: Option<u16>,
|
||||||
|
#[serde(rename = "fastest_time_30_zombies_prison_rip")]
|
||||||
|
pr_r30: Option<u16>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arcade> for Vec<(&'static str, String, bool)> {
|
||||||
|
fn from(arcade: Arcade) -> Vec<(&'static str, String, bool)> {
|
||||||
|
let mut vec = Vec::<(&'static str, String, bool)>::with_capacity(11);
|
||||||
|
|
||||||
|
fn format(sec: u16) -> String {
|
||||||
|
let h = sec / 3600;
|
||||||
|
let m = (sec % 3600) / 60;
|
||||||
|
let s = sec % 60;
|
||||||
|
if h > 0 {
|
||||||
|
format!("{}:{}:{}", h, m, s)
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", m, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(wins) = arcade.wins {
|
||||||
|
vec.push(("Wins", wins.to_string(), true));
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! push_time_if_some {
|
||||||
|
($field:ident, $name:expr) => {
|
||||||
|
if let Some(val) = arcade.$field() {
|
||||||
|
let formatted = format(val);
|
||||||
|
vec.push(($name, formatted, true));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
push_time_if_some!(de_r30, "DE");
|
||||||
|
push_time_if_some!(deh_r30, "DE Hard");
|
||||||
|
push_time_if_some!(der_r30, "DE RIP");
|
||||||
|
push_time_if_some!(bb_r30, "BB");
|
||||||
|
push_time_if_some!(bbh_r30, "BB Hard");
|
||||||
|
push_time_if_some!(bbr_r30, "BB RIP");
|
||||||
|
push_time_if_some!(aa_win, "AA");
|
||||||
|
push_time_if_some!(p_r30, "Prison");
|
||||||
|
push_time_if_some!(ph_r30, "Prison Hard");
|
||||||
|
push_time_if_some!(pr_r30, "Prison RIP");
|
||||||
|
|
||||||
|
vec
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Getters)]
|
||||||
|
struct Stats {
|
||||||
|
#[getset(get)]
|
||||||
|
#[serde(rename = "Arcade")]
|
||||||
|
arcade: Option<Arcade>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Getters)]
|
||||||
|
#[getset(get)]
|
||||||
|
pub struct HypixelPlayer {
|
||||||
#[serde(rename = "socialMedia")]
|
#[serde(rename = "socialMedia")]
|
||||||
social_media: Option<SocialMedia>,
|
social_media: Option<SocialMedia>,
|
||||||
|
stats: Option<Stats>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Arcade {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
wins: None,
|
||||||
|
de_r30: None,
|
||||||
|
deh_r30: None,
|
||||||
|
der_r30: None,
|
||||||
|
bb_r30: None,
|
||||||
|
bbh_r30: None,
|
||||||
|
bbr_r30: None,
|
||||||
|
aa_win: None,
|
||||||
|
p_r30: None,
|
||||||
|
ph_r30: None,
|
||||||
|
pr_r30: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct HypixelResponse {
|
struct HypixelResponse {
|
||||||
#[serde(rename = "player")]
|
#[serde(rename = "player")]
|
||||||
player: HypixelPlayer,
|
player: HypixelPlayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HypixelResponse {
|
impl HypixelPlayer {
|
||||||
pub async fn get(uuid: &str, client: &Client) -> Result<Self, Error> {
|
pub async fn get(uuid: &str, client: &Client) -> Result<Self, Error> {
|
||||||
let player = client
|
let player = client
|
||||||
.get(format!("https://api.hypixel.net/v2/player?uuid={uuid}"))
|
.get(format!("https://api.hypixel.net/v2/player?uuid={uuid}"))
|
||||||
.send()
|
.send()
|
||||||
.await?
|
.await?
|
||||||
.error_for_status()?
|
.error_for_status()?
|
||||||
.json::<Self>()
|
.json::<HypixelResponse>()
|
||||||
.await?;
|
.await?
|
||||||
|
.player;
|
||||||
Ok(player)
|
Ok(player)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn discord(self) -> Option<String> {
|
fn discord(&self) -> Option<&str> {
|
||||||
self.player
|
self.social_media()
|
||||||
.social_media
|
.as_ref()
|
||||||
.and_then(|p| p.links)
|
.and_then(|p| p.links().as_ref())
|
||||||
.and_then(|l| l.discord)
|
.and_then(|l| l.discord().as_ref().map(|s| s.as_str()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_if_discord(self, discord: &str) -> Result<Self, LinkingError> {
|
||||||
|
match self.discord() {
|
||||||
|
Some(d) => {
|
||||||
|
if d == discord {
|
||||||
|
Ok(self)
|
||||||
|
} else {
|
||||||
|
Err(LinkingError::WrongLink(discord.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(LinkingError::NotLinked),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn arcade_stats(&self) -> Option<&Arcade> {
|
||||||
|
self.stats().as_ref().and_then(|s| s.arcade().as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,4 +64,7 @@ cache_hit_handler!(
|
||||||
"https://api.minecraftservices.com/minecraft/profile/lookup/{}"
|
"https://api.minecraftservices.com/minecraft/profile/lookup/{}"
|
||||||
);
|
);
|
||||||
|
|
||||||
cache_hit_handler!(uuid, "https://api.minecraftservices.com/minecraft/profile/lookup/name/{}");
|
cache_hit_handler!(
|
||||||
|
uuid,
|
||||||
|
"https://api.minecraftservices.com/minecraft/profile/lookup/name/{}"
|
||||||
|
);
|
||||||
|
|
47
src/error.rs
47
src/error.rs
|
@ -11,46 +11,77 @@ macro_rules! reply_fail_handler {
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LinkingError {
|
||||||
|
NotLinked,
|
||||||
|
WrongLink(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LinkingError {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
|
match self {
|
||||||
|
Self::NotLinked => write!(
|
||||||
|
f,
|
||||||
|
"This Minecraft account's Hypixel profile has no Discord account linked. Please \
|
||||||
|
follow the steps in <#1256219552568840263>."
|
||||||
|
),
|
||||||
|
Self::WrongLink(name) => write!(
|
||||||
|
f,
|
||||||
|
"This Minecraft account's Hypixel profile has a different Discord account linked. \
|
||||||
|
If you actually own this account, please set the linked discord to `{name}`"
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Sqlx(sqlx::Error),
|
Sqlx(sqlx::Error),
|
||||||
Api(reqwest::Error),
|
Api(reqwest::Error),
|
||||||
Serenity(serenity::Error),
|
Serenity(serenity::Error),
|
||||||
OnCooldown(std::time::Duration),
|
OnCooldown(std::time::Duration),
|
||||||
|
LinkingError(LinkingError),
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Error {
|
impl Display for Error {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
match self {
|
match self {
|
||||||
Error::Sqlx(e) => write!(f, "SQLx Error: {}", e),
|
Self::Sqlx(e) => write!(f, "SQLx Error: {e}"),
|
||||||
Error::Api(e) => write!(f, "HTTPS Error:\n{}", e),
|
Self::Api(e) => write!(f, "HTTPS Error:\n{e}"),
|
||||||
Error::Serenity(e) => write!(f, "Discord Error:\n {}", e),
|
Self::Serenity(e) => write!(f, "Discord Error:\n {e}"),
|
||||||
Error::OnCooldown(d) => write!(
|
Self::OnCooldown(d) => write!(
|
||||||
f,
|
f,
|
||||||
"This command is on cooldown. {}s remaining.",
|
"This command is on cooldown. {}s remaining.",
|
||||||
d.as_secs()
|
d.as_secs()
|
||||||
),
|
),
|
||||||
Error::Other(s) => write!(f, "{}", s),
|
Self::LinkingError(l) => write!(f, "{l}"),
|
||||||
|
Self::Other(s) => write!(f, "{s}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<sqlx::Error> for Error {
|
impl From<sqlx::Error> for Error {
|
||||||
fn from(error: sqlx::Error) -> Self {
|
fn from(error: sqlx::Error) -> Self {
|
||||||
Error::Sqlx(error)
|
Self::Sqlx(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwest::Error> for Error {
|
impl From<reqwest::Error> for Error {
|
||||||
fn from(error: reqwest::Error) -> Self {
|
fn from(error: reqwest::Error) -> Self {
|
||||||
Error::Api(error)
|
Self::Api(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<serenity::Error> for Error {
|
impl From<serenity::Error> for Error {
|
||||||
fn from(error: serenity::Error) -> Self {
|
fn from(error: serenity::Error) -> Self {
|
||||||
Error::Serenity(error)
|
Self::Serenity(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LinkingError> for Error {
|
||||||
|
fn from(value: LinkingError) -> Self {
|
||||||
|
Self::LinkingError(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue