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 serenity::all::ButtonStyle;
|
||||
use serenity::all::{
|
||||
ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateMessage, ReactionType,
|
||||
User,
|
||||
ButtonStyle, ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateEmbed,
|
||||
CreateMessage, ReactionType, User,
|
||||
};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
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::Other;
|
||||
use crate::Caches;
|
||||
use crate::Context;
|
||||
|
||||
|
@ -25,6 +24,16 @@ pub(crate) async fn account(_ctx: Context<'_>) -> Result<(), Error> {
|
|||
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")]
|
||||
/// Verify a Minecraft account on the Zombies MultiPlayer Discord.
|
||||
pub(crate) async fn add(
|
||||
|
@ -33,142 +42,85 @@ pub(crate) async fn add(
|
|||
#[min_length = 2]
|
||||
#[max_length = 16]
|
||||
ign: String,
|
||||
#[description = "Discord User"] user: Option<User>,
|
||||
#[description = "admin-only"] force: Option<bool>,
|
||||
#[description = "Discord User"] target: Option<User>,
|
||||
#[description = "ZMP-admin only"] mode: Option<Mode>,
|
||||
) -> Result<(), Error> {
|
||||
ctx.defer().await?;
|
||||
let force: bool =
|
||||
force.unwrap_or(false) && ctx.framework().options.owners.contains(&ctx.author().id) && {
|
||||
let _ = user.as_ref().ok_or(Other(
|
||||
"Warning: attempted to run forced account add without specifying a target Discord \
|
||||
account."
|
||||
.to_string(),
|
||||
))?;
|
||||
true
|
||||
};
|
||||
let user: User = user.unwrap_or(ctx.author().clone());
|
||||
let mode = if ctx.framework().options().owners.contains(&ctx.author().id) {
|
||||
mode.unwrap_or(Mode::Normal)
|
||||
} else {
|
||||
Mode::Normal
|
||||
};
|
||||
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(
|
||||
ign.as_str(),
|
||||
&ctx.data().clients.general,
|
||||
&ctx.data().caches,
|
||||
)
|
||||
.await?;
|
||||
match force
|
||||
|| uuid
|
||||
.has_discord_user(&user, &ctx.data().clients.hypixel_api_client)
|
||||
.await?
|
||||
{
|
||||
true => {
|
||||
let pool = &ctx.data().sqlite_pool;
|
||||
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)
|
||||
let link_status = match mode {
|
||||
Mode::Normal => {
|
||||
let profile = uuid
|
||||
.hypixel_player_data(&ctx.data().clients.hypixel_api_client)
|
||||
.await?
|
||||
.map_if_discord(user.name.as_str())?;
|
||||
let done = link(&ctx.data().sqlite_pool, uuid, &user).await?;
|
||||
ChannelId::new(1257776992497959075_u64)
|
||||
.send_message(
|
||||
ctx,
|
||||
CreateMessage::new()
|
||||
.content(s)
|
||||
.allowed_mentions(
|
||||
CreateAllowedMentions::new().empty_roles().all_users(true),
|
||||
)
|
||||
.components(vec![CreateActionRow::Buttons(vec![
|
||||
CreateButton::new("accept_verification")
|
||||
.emoji(ReactionType::from('✅'))
|
||||
.style(ButtonStyle::Secondary),
|
||||
CreateButton::new("deny_verification")
|
||||
.emoji(ReactionType::from('❌'))
|
||||
.style(ButtonStyle::Secondary),
|
||||
CreateButton::new("list_accounts")
|
||||
.emoji(ReactionType::from('📜'))
|
||||
.style(ButtonStyle::Primary),
|
||||
])]),
|
||||
create_verification_message(profile.arcade_stats().copied().unwrap_or_default(), &user, ign)
|
||||
)
|
||||
.await?;
|
||||
ctx.send(CreateReply::default().content(status)).await?;
|
||||
Ok(())
|
||||
done
|
||||
}
|
||||
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
|
||||
))),
|
||||
}
|
||||
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![
|
||||
CreateButton::new("accept_verification")
|
||||
.emoji(ReactionType::from('✅'))
|
||||
.style(ButtonStyle::Secondary),
|
||||
CreateButton::new("deny_verification")
|
||||
.emoji(ReactionType::from('❌'))
|
||||
.style(ButtonStyle::Secondary),
|
||||
CreateButton::new("list_accounts")
|
||||
.emoji(ReactionType::from('📜'))
|
||||
.style(ButtonStyle::Primary),
|
||||
])])
|
||||
}
|
||||
|
||||
#[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::ListType::*;
|
||||
use crate::data::mojang::name;
|
||||
|
@ -12,7 +13,6 @@ use poise::serenity_prelude::CreateInteractionResponse;
|
|||
use poise::serenity_prelude::CreateInteractionResponseMessage;
|
||||
use poise::CreateReply;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use crate::data::account_links::{Link, Uuid};
|
||||
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
|
@ -26,10 +26,12 @@ pub(crate) async fn helpstart(ctx: Context<'_>, user: Option<String>) -> Result<
|
|||
let mc_accounts = match user {
|
||||
None => {
|
||||
futures::future::try_join_all(
|
||||
Vec::<Uuid>::from(Link::try_from_discord(ctx.author(), &ctx.data().sqlite_pool).await?)
|
||||
.into_iter()
|
||||
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.get()))
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::<Uuid>::from(
|
||||
Link::try_from_discord(ctx.author(), &ctx.data().sqlite_pool).await?,
|
||||
)
|
||||
.into_iter()
|
||||
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.uuid))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.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 usable = bots
|
||||
.iter()
|
||||
.filter(|b| match b.list_type() {
|
||||
Whitelist => b.list().iter().any(|w| mc_accounts.contains(w)),
|
||||
Blacklist => mc_accounts.iter().any(|m| !b.list().contains(m)),
|
||||
.filter(|bot| match bot.list_type() {
|
||||
Whitelist => bot.list().iter().map(|wl| wl.to_lowercase()).any(|wl| {
|
||||
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<_>>();
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ pub enum Difficulty {
|
|||
#[poise::command(
|
||||
slash_command,
|
||||
install_context = "Guild",
|
||||
interaction_context = "Guild",
|
||||
interaction_context = "Guild"
|
||||
)]
|
||||
/// Find a team for Hypixel Zombies.
|
||||
pub(crate) async fn lfg(
|
||||
|
@ -103,7 +103,10 @@ pub(crate) async fn lfg(
|
|||
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 {
|
||||
Normal => {}
|
||||
Difficulty::Hard | Difficulty::Rip => {
|
||||
|
@ -118,7 +121,7 @@ pub(crate) async fn lfg(
|
|||
}
|
||||
}
|
||||
|
||||
let reply = CreateMessage::default()
|
||||
let reply = CreateMessage::default()
|
||||
.content(reply_content)
|
||||
.allowed_mentions(CreateAllowedMentions::new().roles(vec![ping]));
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use super::hypixel::HypixelResponse;
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::data::hypixel::HypixelPlayer;
|
||||
use crate::Caches;
|
||||
use crate::Error::{self, *};
|
||||
use crate::Error;
|
||||
use poise::serenity_prelude::User;
|
||||
use reqwest::Client;
|
||||
use sqlx::query_as;
|
||||
|
@ -9,14 +11,16 @@ use sqlx::Sqlite;
|
|||
|
||||
#[derive(PartialEq, sqlx::FromRow)]
|
||||
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 {
|
||||
pub(crate) fn get(self) -> String {
|
||||
self.uuid
|
||||
}
|
||||
|
||||
pub(crate) fn as_str(&self) -> &str {
|
||||
self.uuid.as_str()
|
||||
}
|
||||
|
@ -30,21 +34,13 @@ impl Uuid {
|
|||
let ign = crate::data::mojang::name(c, cli, self.uuid.clone()).await?;
|
||||
Ok(ign)
|
||||
}
|
||||
}
|
||||
|
||||
impl Uuid {
|
||||
pub(crate) async fn has_discord_user(
|
||||
pub(crate) async fn hypixel_player_data(
|
||||
&self,
|
||||
user: &User,
|
||||
client: &Client,
|
||||
) -> Result<bool, Error> {
|
||||
let res = HypixelResponse::get(self.uuid.as_str(), client).await?;
|
||||
let matches = res.discord().ok_or(Other(
|
||||
"The Hypixel profile has no Discord account linked. Please follow the steps in \
|
||||
<#1256219552568840263>"
|
||||
.to_string(),
|
||||
))? == user.name;
|
||||
Ok(matches)
|
||||
) -> Result<HypixelPlayer, Error> {
|
||||
let p = HypixelPlayer::get(self.uuid.as_str(), client).await?;
|
||||
Ok(p)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +127,7 @@ impl Link {
|
|||
* let link_id = LinkId::try_from_minecraft(pool, player.uuid.as_str()).await?;
|
||||
* Link::lookup_by_id(link_id, pool).await
|
||||
* }
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -186,7 +182,9 @@ impl LinkId {
|
|||
)
|
||||
.fetch_optional(pool)
|
||||
.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(
|
||||
|
@ -201,6 +199,79 @@ impl LinkId {
|
|||
)
|
||||
.fetch_optional(pool)
|
||||
.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 serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Getters)]
|
||||
#[getset(get)]
|
||||
struct Links {
|
||||
#[serde(rename = "DISCORD")]
|
||||
discord: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Getters)]
|
||||
#[getset(get)]
|
||||
struct SocialMedia {
|
||||
links: Option<Links>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct HypixelPlayer {
|
||||
#[derive(Deserialize, CopyGetters, Clone, Copy)]
|
||||
#[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")]
|
||||
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)]
|
||||
pub struct HypixelResponse {
|
||||
struct HypixelResponse {
|
||||
#[serde(rename = "player")]
|
||||
player: HypixelPlayer,
|
||||
}
|
||||
|
||||
impl HypixelResponse {
|
||||
impl HypixelPlayer {
|
||||
pub async fn get(uuid: &str, client: &Client) -> Result<Self, Error> {
|
||||
let player = client
|
||||
.get(format!("https://api.hypixel.net/v2/player?uuid={uuid}"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json::<Self>()
|
||||
.await?;
|
||||
.json::<HypixelResponse>()
|
||||
.await?
|
||||
.player;
|
||||
Ok(player)
|
||||
}
|
||||
|
||||
pub fn discord(self) -> Option<String> {
|
||||
self.player
|
||||
.social_media
|
||||
.and_then(|p| p.links)
|
||||
.and_then(|l| l.discord)
|
||||
fn discord(&self) -> Option<&str> {
|
||||
self.social_media()
|
||||
.as_ref()
|
||||
.and_then(|p| p.links().as_ref())
|
||||
.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/{}"
|
||||
);
|
||||
|
||||
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)]
|
||||
pub enum Error {
|
||||
Sqlx(sqlx::Error),
|
||||
Api(reqwest::Error),
|
||||
Serenity(serenity::Error),
|
||||
OnCooldown(std::time::Duration),
|
||||
LinkingError(LinkingError),
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
match self {
|
||||
Error::Sqlx(e) => write!(f, "SQLx Error: {}", e),
|
||||
Error::Api(e) => write!(f, "HTTPS Error:\n{}", e),
|
||||
Error::Serenity(e) => write!(f, "Discord Error:\n {}", e),
|
||||
Error::OnCooldown(d) => write!(
|
||||
Self::Sqlx(e) => write!(f, "SQLx Error: {e}"),
|
||||
Self::Api(e) => write!(f, "HTTPS Error:\n{e}"),
|
||||
Self::Serenity(e) => write!(f, "Discord Error:\n {e}"),
|
||||
Self::OnCooldown(d) => write!(
|
||||
f,
|
||||
"This command is on cooldown. {}s remaining.",
|
||||
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 {
|
||||
fn from(error: sqlx::Error) -> Self {
|
||||
Error::Sqlx(error)
|
||||
Self::Sqlx(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
Error::Api(error)
|
||||
Self::Api(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serenity::Error> for Error {
|
||||
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