Compare commits
2 commits
8442ddb672
...
0e6f4fe56c
Author | SHA1 | Date | |
---|---|---|---|
0e6f4fe56c | |||
6eb7311055 |
6 changed files with 283 additions and 242 deletions
|
@ -1,142 +1,19 @@
|
||||||
use poise::CreateReply;
|
use poise::CreateReply;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::Deserialize;
|
|
||||||
use serenity::all::ButtonStyle;
|
use serenity::all::ButtonStyle;
|
||||||
use serenity::all::{
|
use serenity::all::{
|
||||||
ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateMessage, ReactionType,
|
ChannelId, CreateActionRow, CreateAllowedMentions, CreateButton, CreateMessage, ReactionType,
|
||||||
User,
|
User,
|
||||||
};
|
};
|
||||||
use sqlx::{query_as, Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
use std::ops::Add;
|
|
||||||
|
|
||||||
use crate::commands::command_helper::cooldown;
|
use crate::commands::command_helper::cooldown;
|
||||||
|
use crate::data::account_links::{Link, LinkId, Uuid};
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::error::Error::Other;
|
use crate::error::Error::Other;
|
||||||
use crate::Caches;
|
use crate::Caches;
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Links {
|
|
||||||
#[serde(rename = "DISCORD")]
|
|
||||||
pub discord: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct SocialMedia {
|
|
||||||
pub links: Option<Links>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct HypixelPlayer {
|
|
||||||
#[serde(rename = "socialMedia")]
|
|
||||||
pub social_media: Option<SocialMedia>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct HypixelResponse {
|
|
||||||
#[serde(rename = "player")]
|
|
||||||
pub player: HypixelPlayer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, sqlx::FromRow)]
|
|
||||||
pub(crate) struct Uuid {
|
|
||||||
pub(crate) uuid: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Uuid {
|
|
||||||
fn get(&self) -> &str {
|
|
||||||
self.uuid.as_str()
|
|
||||||
}
|
|
||||||
async fn for_ign(ign: &String, cli: &Client, c: &Caches) -> Result<Self, Error> {
|
|
||||||
let uuid = crate::data::mojang::uuid(c, cli, ign.clone()).await?;
|
|
||||||
Ok(Self { uuid })
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn ign(&self, c: &Caches, cli: &Client) -> Result<String, Error> {
|
|
||||||
let ign = crate::data::mojang::name(c, cli, self.uuid.clone()).await?;
|
|
||||||
Ok(ign)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
struct DiscordId {
|
|
||||||
id: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Uuid {
|
|
||||||
async fn has_discord_user(&self, user: &User, client: &Client) -> Result<bool, Error> {
|
|
||||||
let url: String = format!("https://api.hypixel.net/v2/player?uuid={}", self.uuid);
|
|
||||||
let res: HypixelResponse = client
|
|
||||||
.get(url)
|
|
||||||
.send()
|
|
||||||
.await?
|
|
||||||
.error_for_status()?
|
|
||||||
.json::<HypixelResponse>()
|
|
||||||
.await?;
|
|
||||||
let matches = res
|
|
||||||
.player
|
|
||||||
.social_media
|
|
||||||
.and_then(|sm| sm.links)
|
|
||||||
.and_then(|l| l.discord)
|
|
||||||
.ok_or(Other(
|
|
||||||
"The Hypixel profile has no Discord account linked. Please follow the steps in \
|
|
||||||
<#1256219552568840263>"
|
|
||||||
.to_string(),
|
|
||||||
))?
|
|
||||||
== user.name;
|
|
||||||
Ok(matches)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a, R: sqlx::Row> sqlx::FromRow<'a, R> for DiscordId
|
|
||||||
where
|
|
||||||
&'a ::std::primitive::str: sqlx::ColumnIndex<R>,
|
|
||||||
i64: ::sqlx::decode::Decode<'a, R::Database>,
|
|
||||||
i64: ::sqlx::types::Type<R::Database>,
|
|
||||||
{
|
|
||||||
fn from_row(row: &'a R) -> sqlx::Result<Self> {
|
|
||||||
let discord_id: i64 = row.try_get("discord_id")?;
|
|
||||||
Ok(DiscordId {
|
|
||||||
id: discord_id.cast_unsigned(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) struct Link {
|
|
||||||
link_id: u16,
|
|
||||||
discord_ids: Vec<DiscordId>,
|
|
||||||
pub(crate) minecraft_accounts: Vec<Uuid>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Link {
|
|
||||||
fn new(link_id: u16) -> Self {
|
|
||||||
Link {
|
|
||||||
link_id,
|
|
||||||
discord_ids: vec![],
|
|
||||||
minecraft_accounts: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async fn minecraft(mut self, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
|
||||||
let link_id: i16 = self.link_id.cast_signed();
|
|
||||||
self.minecraft_accounts = query_as(
|
|
||||||
format!(
|
|
||||||
"SELECT minecraft_uuid AS uuid FROM minecraft_links WHERE link_id = {link_id};"
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
async fn discord(mut self, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
|
||||||
let link_id: i16 = self.link_id.cast_signed();
|
|
||||||
self.discord_ids = query_as(
|
|
||||||
format!("SELECT discord_id FROM discord_links WHERE link_id = {link_id};").as_str(),
|
|
||||||
)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
subcommands("add", "list"),
|
subcommands("add", "list"),
|
||||||
|
@ -170,7 +47,12 @@ pub(crate) async fn add(
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
let user: User = user.unwrap_or(ctx.author().clone());
|
let user: User = user.unwrap_or(ctx.author().clone());
|
||||||
let uuid: Uuid = Uuid::for_ign(&ign, &ctx.data().clients.general, &ctx.data().caches).await?;
|
let uuid: Uuid = Uuid::for_ign(
|
||||||
|
ign.as_str(),
|
||||||
|
&ctx.data().clients.general,
|
||||||
|
&ctx.data().caches,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
match force
|
match force
|
||||||
|| uuid
|
|| uuid
|
||||||
.has_discord_user(&user, &ctx.data().clients.hypixel_api_client)
|
.has_discord_user(&user, &ctx.data().clients.hypixel_api_client)
|
||||||
|
@ -178,14 +60,13 @@ pub(crate) async fn add(
|
||||||
{
|
{
|
||||||
true => {
|
true => {
|
||||||
let pool = &ctx.data().sqlite_pool;
|
let pool = &ctx.data().sqlite_pool;
|
||||||
let status: &str = match link_id_from_minecraft(pool, uuid.get()).await {
|
let status: &str = match LinkId::try_from_minecraft(pool, uuid.as_str()).await {
|
||||||
None => match link_id_from_discord(pool, user.id.get()).await {
|
Err(_) => match LinkId::try_from_discord(pool, user.id.get()).await {
|
||||||
None => {
|
Err(_) => {
|
||||||
let id = new_link_id(pool).await?;
|
let id = LinkId::new(pool).await?;
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
format!(
|
format!(
|
||||||
"INSERT INTO discord_links VALUES ({}, {});",
|
"INSERT INTO discord_links VALUES ({id}, {});",
|
||||||
id.inner,
|
|
||||||
user.id.get()
|
user.id.get()
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
|
@ -194,8 +75,7 @@ pub(crate) async fn add(
|
||||||
.await?;
|
.await?;
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
format!(
|
format!(
|
||||||
"INSERT INTO minecraft_links VALUES ({}, \"{}\");",
|
"INSERT INTO minecraft_links VALUES ({id}, \"{}\");",
|
||||||
id.inner,
|
|
||||||
uuid.get()
|
uuid.get()
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
|
@ -204,11 +84,10 @@ pub(crate) async fn add(
|
||||||
.await?;
|
.await?;
|
||||||
"Linked your Discord and Minecraft account."
|
"Linked your Discord and Minecraft account."
|
||||||
}
|
}
|
||||||
Some(dc_id) => {
|
Ok(dc_id) => {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
format!(
|
format!(
|
||||||
"INSERT INTO minecraft_links VALUES ({}, \"{}\");",
|
"INSERT INTO minecraft_links VALUES ({dc_id}, \"{}\");",
|
||||||
dc_id.inner,
|
|
||||||
uuid.get()
|
uuid.get()
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
|
@ -219,12 +98,11 @@ pub(crate) async fn add(
|
||||||
link."
|
link."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(mc_id) => match link_id_from_discord(pool, user.id.get()).await {
|
Ok(mc_id) => match LinkId::try_from_discord(pool, user.id.get()).await {
|
||||||
None => {
|
Err(_) => {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
format!(
|
format!(
|
||||||
"INSERT INTO discord_links VALUES ({}, {});",
|
"INSERT INTO discord_links VALUES ({mc_id}, {});",
|
||||||
mc_id.inner,
|
|
||||||
user.id.get()
|
user.id.get()
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
|
@ -234,11 +112,10 @@ pub(crate) async fn add(
|
||||||
"Your Minecraft account has previously had an account linked. Added the \
|
"Your Minecraft account has previously had an account linked. Added the \
|
||||||
new link."
|
new link."
|
||||||
}
|
}
|
||||||
Some(dc_id) => {
|
Ok(dc_id) => {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
format!(
|
format!(
|
||||||
"UPDATE minecraft_links SET link_id = {} WHERE link_id = {};",
|
"UPDATE minecraft_links SET link_id = {mc_id} WHERE link_id = {dc_id};",
|
||||||
mc_id.inner, dc_id.inner
|
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
|
@ -246,8 +123,7 @@ pub(crate) async fn add(
|
||||||
.await?;
|
.await?;
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
format!(
|
format!(
|
||||||
"UPDATE discord_links SET link_id = {} WHERE link_id = {};",
|
"UPDATE discord_links SET link_id = {mc_id} WHERE link_id = {dc_id};",
|
||||||
mc_id.inner, dc_id.inner
|
|
||||||
)
|
)
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
|
@ -316,37 +192,24 @@ pub(crate) async fn list(ctx: Context<'_>, user: User) -> Result<(), Error> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn get_link(user: &User, pool: &Pool<Sqlite>) -> Result<Link, Error> {
|
|
||||||
let link_id: u16 = link_id_from_discord(pool, user.id.get())
|
|
||||||
.await
|
|
||||||
.expect("This user has no linked accounts")
|
|
||||||
.into();
|
|
||||||
let link = Link::new(link_id)
|
|
||||||
.minecraft(pool)
|
|
||||||
.await?
|
|
||||||
.discord(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(link)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn list_string(
|
pub(crate) async fn list_string(
|
||||||
pool: &Pool<Sqlite>,
|
pool: &Pool<Sqlite>,
|
||||||
user: &User,
|
user: &User,
|
||||||
c: &Caches,
|
c: &Caches,
|
||||||
cli: &Client,
|
cli: &Client,
|
||||||
) -> Result<String, Error> {
|
) -> Result<String, Error> {
|
||||||
let link = get_link(user, pool).await?;
|
let link = Link::try_from_discord(user, pool).await?;
|
||||||
let mut discord_list = String::from("### Discord:");
|
let mut discord_list = String::from("### Discord:");
|
||||||
for dc in link.discord_ids {
|
for dc in link.discord_ids() {
|
||||||
discord_list.push_str(format!("\n- <@{}>", dc.id).as_str());
|
discord_list.push_str(format!("\n- <@{dc}>").as_str());
|
||||||
}
|
}
|
||||||
let mut minecraft_list = String::from("### Minecraft:");
|
let mut minecraft_list = String::from("### Minecraft:");
|
||||||
for mc in link.minecraft_accounts {
|
for mc in link.minecraft_accounts() {
|
||||||
minecraft_list.push_str(format!("\n- `{}`", mc.ign(c, cli).await?).as_str());
|
minecraft_list.push_str(format!("\n- `{}`", mc.ign(c, cli).await?).as_str());
|
||||||
}
|
}
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"## Account list for member #{}:\n{}\n{}",
|
"## Account list for member #{}:\n{}\n{}",
|
||||||
link.link_id,
|
link.link_id(),
|
||||||
discord_list.as_str(),
|
discord_list.as_str(),
|
||||||
minecraft_list.as_str()
|
minecraft_list.as_str()
|
||||||
))
|
))
|
||||||
|
@ -356,64 +219,3 @@ pub(crate) async fn list_string(
|
||||||
pub(crate) async fn remove(_ctx: Context<'_>) -> Result<(), Error> {
|
pub(crate) async fn remove(_ctx: Context<'_>) -> Result<(), Error> {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn link_id_from_minecraft(pool: &Pool<Sqlite>, minecraft_uuid: &str) -> Option<LinkId> {
|
|
||||||
query_as(
|
|
||||||
format!(
|
|
||||||
"SELECT link_id FROM minecraft_links WHERE minecraft_uuid = \"{minecraft_uuid}\" \
|
|
||||||
LIMIT 1;"
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await
|
|
||||||
.expect("Database error: fetching link id by uuid")
|
|
||||||
}
|
|
||||||
async fn link_id_from_discord(pool: &Pool<Sqlite>, snowflake: u64) -> Option<LinkId> {
|
|
||||||
query_as(
|
|
||||||
format!(
|
|
||||||
"SELECT link_id FROM discord_links WHERE discord_id = {} LIMIT 1;",
|
|
||||||
snowflake.cast_signed()
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
)
|
|
||||||
.fetch_optional(pool)
|
|
||||||
.await
|
|
||||||
.expect("Database error: fetching link_id for discord_id")
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(sqlx::FromRow)]
|
|
||||||
struct LinkId {
|
|
||||||
#[sqlx(rename = "link_id")]
|
|
||||||
inner: i16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u16> for LinkId {
|
|
||||||
fn from(unsigned: u16) -> Self {
|
|
||||||
Self {
|
|
||||||
inner: unsigned.cast_signed(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<u16> for LinkId {
|
|
||||||
fn into(self) -> u16 {
|
|
||||||
self.inner.cast_unsigned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Add<i16> for LinkId {
|
|
||||||
type Output = LinkId;
|
|
||||||
|
|
||||||
fn add(mut self, rhs: i16) -> Self::Output {
|
|
||||||
self.inner += rhs;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn new_link_id(pool: &Pool<Sqlite>) -> Result<LinkId, Error> {
|
|
||||||
let result: LinkId = query_as("SELECT MAX(link_id) AS link_id FROM minecraft_links;")
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(result + 1)
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ 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,
|
||||||
|
@ -22,14 +23,12 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
// Check for bots available to you.
|
// Check for bots available to you.
|
||||||
pub(crate) async fn helpstart(ctx: Context<'_>, user: Option<String>) -> Result<(), Error> {
|
pub(crate) async fn helpstart(ctx: Context<'_>, user: Option<String>) -> Result<(), Error> {
|
||||||
ctx.defer_ephemeral().await?;
|
ctx.defer_ephemeral().await?;
|
||||||
let links = super::accountv2::get_link(ctx.author(), &ctx.data().sqlite_pool).await?;
|
|
||||||
let mc_accounts = match user {
|
let mc_accounts = match user {
|
||||||
None => {
|
None => {
|
||||||
futures::future::try_join_all(
|
futures::future::try_join_all(
|
||||||
links
|
Vec::<Uuid>::from(Link::try_from_discord(ctx.author(), &ctx.data().sqlite_pool).await?)
|
||||||
.minecraft_accounts
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.uuid))
|
.map(|a| name(&ctx.data().caches, &ctx.data().clients.general, a.get()))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
|
|
|
@ -50,7 +50,6 @@ pub enum Difficulty {
|
||||||
slash_command,
|
slash_command,
|
||||||
install_context = "Guild",
|
install_context = "Guild",
|
||||||
interaction_context = "Guild",
|
interaction_context = "Guild",
|
||||||
ephemeral = "false"
|
|
||||||
)]
|
)]
|
||||||
/// Find a team for Hypixel Zombies.
|
/// Find a team for Hypixel Zombies.
|
||||||
pub(crate) async fn lfg(
|
pub(crate) async fn lfg(
|
||||||
|
@ -76,7 +75,7 @@ pub(crate) async fn lfg(
|
||||||
#[rename = "message"]
|
#[rename = "message"]
|
||||||
note: Option<String>,
|
note: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
ctx.defer().await?;
|
cooldown(&ctx, 600, 300)?;
|
||||||
let current: u8 = current_players.unwrap_or(1);
|
let current: u8 = current_players.unwrap_or(1);
|
||||||
let mut desired: u8 = desired_players.unwrap_or(4);
|
let mut desired: u8 = desired_players.unwrap_or(4);
|
||||||
if current >= desired {
|
if current >= desired {
|
||||||
|
@ -122,7 +121,6 @@ pub(crate) async fn lfg(
|
||||||
.ephemeral(false)
|
.ephemeral(false)
|
||||||
.allowed_mentions(CreateAllowedMentions::new().roles(vec![ping]));
|
.allowed_mentions(CreateAllowedMentions::new().roles(vec![ping]));
|
||||||
|
|
||||||
cooldown(&ctx, 600, 300)?;
|
|
||||||
ctx.send(reply).await?;
|
ctx.send(reply).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -145,7 +143,6 @@ enum ExpertMap {
|
||||||
slash_command,
|
slash_command,
|
||||||
install_context = "Guild",
|
install_context = "Guild",
|
||||||
interaction_context = "Guild",
|
interaction_context = "Guild",
|
||||||
ephemeral = "false",
|
|
||||||
rename = "lfg-expert"
|
rename = "lfg-expert"
|
||||||
)]
|
)]
|
||||||
/// Find a team of skilled players.
|
/// Find a team of skilled players.
|
||||||
|
@ -224,14 +221,6 @@ pub(crate) async fn expert(
|
||||||
.ephemeral(true),
|
.ephemeral(true),
|
||||||
};
|
};
|
||||||
ctx.send(reply).await?;
|
ctx.send(reply).await?;
|
||||||
ctx.send(
|
|
||||||
CreateReply::default()
|
|
||||||
.content(
|
|
||||||
"Please be aware of the musava helpstart project. Use /helpstart as reference.",
|
|
||||||
)
|
|
||||||
.ephemeral(true),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,7 +234,6 @@ enum OtherPing {
|
||||||
slash_command,
|
slash_command,
|
||||||
install_context = "Guild",
|
install_context = "Guild",
|
||||||
interaction_context = "Guild",
|
interaction_context = "Guild",
|
||||||
ephemeral = "false",
|
|
||||||
rename = "lfg-other"
|
rename = "lfg-other"
|
||||||
)]
|
)]
|
||||||
/// Find people to play other games with.
|
/// Find people to play other games with.
|
||||||
|
|
|
@ -1 +1,206 @@
|
||||||
|
use super::hypixel::HypixelResponse;
|
||||||
|
use crate::Caches;
|
||||||
|
use crate::Error::{self, *};
|
||||||
|
use poise::serenity_prelude::User;
|
||||||
|
use reqwest::Client;
|
||||||
|
use sqlx::query_as;
|
||||||
|
use sqlx::Pool;
|
||||||
|
use sqlx::Sqlite;
|
||||||
|
|
||||||
|
#[derive(PartialEq, sqlx::FromRow)]
|
||||||
|
pub(crate) struct Uuid {
|
||||||
|
uuid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uuid {
|
||||||
|
pub(crate) fn get(self) -> String {
|
||||||
|
self.uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn as_str(&self) -> &str {
|
||||||
|
self.uuid.as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn for_ign(ign: &str, cli: &Client, c: &Caches) -> Result<Self, Error> {
|
||||||
|
let uuid = crate::data::mojang::uuid(c, cli, ign.to_owned()).await?;
|
||||||
|
Ok(Self { uuid })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn ign(&self, c: &Caches, cli: &Client) -> Result<String, Error> {
|
||||||
|
let ign = crate::data::mojang::name(c, cli, self.uuid.clone()).await?;
|
||||||
|
Ok(ign)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Uuid {
|
||||||
|
pub(crate) async fn has_discord_user(
|
||||||
|
&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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub(crate) struct DiscordId {
|
||||||
|
inner: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: sqlx::Row> sqlx::FromRow<'a, R> for DiscordId
|
||||||
|
where
|
||||||
|
&'a ::std::primitive::str: sqlx::ColumnIndex<R>,
|
||||||
|
i64: ::sqlx::decode::Decode<'a, R::Database>,
|
||||||
|
i64: ::sqlx::types::Type<R::Database>,
|
||||||
|
{
|
||||||
|
fn from_row(row: &'a R) -> sqlx::Result<Self> {
|
||||||
|
let discord_id: i64 = row.try_get("discord_id")?;
|
||||||
|
Ok(DiscordId {
|
||||||
|
inner: discord_id.cast_unsigned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for DiscordId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Link {
|
||||||
|
link_id: LinkId,
|
||||||
|
discord_ids: Vec<DiscordId>,
|
||||||
|
minecraft_accounts: Vec<Uuid>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Link> for Vec<Uuid> {
|
||||||
|
fn from(value: Link) -> Self {
|
||||||
|
value.minecraft_accounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Link {
|
||||||
|
pub(crate) fn minecraft_accounts(&self) -> &Vec<Uuid> {
|
||||||
|
&self.minecraft_accounts
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn discord_ids(&self) -> &Vec<DiscordId> {
|
||||||
|
&self.discord_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn link_id(&self) -> LinkId {
|
||||||
|
self.link_id
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn lookup_by_id(link_id: LinkId, pool: &Pool<Sqlite>) -> Result<Link, Error> {
|
||||||
|
let link = Link {
|
||||||
|
link_id,
|
||||||
|
discord_ids: query_as(
|
||||||
|
format!("SELECT discord_id FROM discord_links WHERE link_id = {link_id};").as_str(),
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?,
|
||||||
|
minecraft_accounts: query_as(
|
||||||
|
format!(
|
||||||
|
"SELECT minecraft_uuid AS uuid FROM minecraft_links WHERE link_id = {link_id};"
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?,
|
||||||
|
};
|
||||||
|
Ok(link)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn try_from_discord(user: &User, pool: &Pool<Sqlite>) -> Result<Self, Error> {
|
||||||
|
let link_id = LinkId::try_from_discord(pool, user.id.get()).await?;
|
||||||
|
Link::lookup_by_id(link_id, pool).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #[allow(dead_code)]
|
||||||
|
* pub(crate) async fn try_from_minecraft(
|
||||||
|
* player: Uuid,
|
||||||
|
* pool: &Pool<Sqlite>,
|
||||||
|
* ) -> Result<Self, Error> {
|
||||||
|
* let link_id = LinkId::try_from_minecraft(pool, player.uuid.as_str()).await?;
|
||||||
|
* Link::lookup_by_id(link_id, pool).await
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct LinkId {
|
||||||
|
inner: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, R: sqlx::Row> sqlx::FromRow<'a, R> for LinkId
|
||||||
|
where
|
||||||
|
&'a ::std::primitive::str: sqlx::ColumnIndex<R>,
|
||||||
|
i16: ::sqlx::decode::Decode<'a, R::Database>,
|
||||||
|
i16: ::sqlx::types::Type<R::Database>,
|
||||||
|
{
|
||||||
|
fn from_row(row: &'a R) -> sqlx::Result<Self> {
|
||||||
|
let link_id: i16 = row.try_get("link_id")?;
|
||||||
|
Ok(Self {
|
||||||
|
inner: link_id.cast_unsigned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for LinkId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LinkId {
|
||||||
|
fn incremented(self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner + 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn new(pool: &Pool<Sqlite>) -> Result<LinkId, Error> {
|
||||||
|
let highest: LinkId = query_as("SELECT MAX(link_id) AS link_id FROM minecraft_links;")
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
Ok(highest.incremented())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn try_from_discord(
|
||||||
|
pool: &Pool<Sqlite>,
|
||||||
|
snowflake: u64,
|
||||||
|
) -> Result<LinkId, Error> {
|
||||||
|
query_as(
|
||||||
|
format!(
|
||||||
|
"SELECT link_id FROM discord_links WHERE discord_id = {} LIMIT 1;",
|
||||||
|
snowflake.cast_signed()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?
|
||||||
|
.ok_or(Other("This user has no accounts linked.".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn try_from_minecraft(
|
||||||
|
pool: &Pool<Sqlite>,
|
||||||
|
minecraft_uuid: &str,
|
||||||
|
) -> Result<LinkId, Error> {
|
||||||
|
query_as(
|
||||||
|
format!(
|
||||||
|
r#"SELECT link_id FROM minecraft_links WHERE minecraft_uuid = "{minecraft_uuid}" LIMIT 1;"#
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?
|
||||||
|
.ok_or(Other("This player has no accounts linked.".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
46
src/data/hypixel.rs
Normal file
46
src/data/hypixel.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use crate::Error;
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Links {
|
||||||
|
#[serde(rename = "DISCORD")]
|
||||||
|
discord: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SocialMedia {
|
||||||
|
links: Option<Links>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct HypixelPlayer {
|
||||||
|
#[serde(rename = "socialMedia")]
|
||||||
|
social_media: Option<SocialMedia>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct HypixelResponse {
|
||||||
|
#[serde(rename = "player")]
|
||||||
|
player: HypixelPlayer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HypixelResponse {
|
||||||
|
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?;
|
||||||
|
Ok(player)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discord(self) -> Option<String> {
|
||||||
|
self.player
|
||||||
|
.social_media
|
||||||
|
.and_then(|p| p.links)
|
||||||
|
.and_then(|l| l.discord)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub(crate) mod account_links;
|
pub(crate) mod account_links;
|
||||||
pub(crate) mod helpstart_api;
|
pub(crate) mod helpstart_api;
|
||||||
|
pub(crate) mod hypixel;
|
||||||
pub(crate) mod mojang;
|
pub(crate) mod mojang;
|
||||||
|
|
Loading…
Add table
Reference in a new issue