This commit is contained in:
Stachelbeere1248 2024-07-01 22:40:51 +02:00
parent 6b24d5ffe5
commit 8e5ac7112a
Signed by: Stachelbeere1248
SSH key fingerprint: SHA256:IozEKdw2dB8TZxkpPdMxcWSoWTIMwoLaCcZJ1AJnY2o
8 changed files with 1487 additions and 124 deletions

1285
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -13,3 +13,7 @@ env_logger = "0.11.3"
anyhow = "1.0.86"
tracing = "0.1.40"
regex = "1.10.4"
sqlx = { version = "0.7.4" , features = ["sqlite", "sqlx-sqlite", "runtime-tokio"]}
reqwest = { version = "0.12.5" }
serde = { version = "1.0.203", features = ["derive"] }
serde_json = { version = "1.0.119" }

BIN
accounts.db Normal file

Binary file not shown.

241
src/commands/account.rs Normal file
View file

@ -0,0 +1,241 @@
use poise::CreateReply;
use serde::{Deserialize, Serialize};
use serenity::all::User;
use sqlx::{query_as, Executor, Pool, Sqlite};
use crate::{Context, Error};
#[poise::command(slash_command, subcommands("add", "list"))]
pub(crate) async fn account(_ctx: Context<'_>) -> Result<(), Error> {
Ok(())
}
#[poise::command(slash_command)]
pub(crate) async fn add(ctx: Context<'_>, ign: String, user: Option<User>) -> Result<(), Error> {
let user_id = user.clone().map(|user| user.id.get());
let user_name = user.clone().map(|user| user.name);
let author_name = ctx.author().name.clone();
let pool = ctx.data().sqlite_pool.clone();
let minecraft_uuid = minecraft_uuid_for_username(ign).await?;
let hypixel_linked_discord = linked_discord_for_uuid(
ctx.data().hypixel_api_client.clone(),
minecraft_uuid.as_str(),
)
.await?;
if hypixel_linked_discord.eq(&user_name.unwrap_or(author_name)) {
link(
user_id.unwrap_or(ctx.author().id.get()),
minecraft_uuid.as_str(),
&pool,
)
.await;
if let Err(why) = ctx
.send(CreateReply::default().content("Linked accounts."))
.await
{
println!("Error sending message: {why}");
}
}
Ok(())
}
#[poise::command(slash_command)]
pub(crate) async fn remove(ctx: Context<'_>) -> Result<(), Error> {
ctx.say("hi").await?;
Ok(())
}
#[poise::command(slash_command)]
pub(crate) async fn list(ctx: Context<'_>, user: Option<User>) -> Result<(), Error> {
let user_id = user.clone().map(|user| user.id.get());
let user_name = user.clone().map(|user| user.name);
let author_name = ctx.author().name.clone();
let pool = ctx.data().sqlite_pool.clone();
let link_id = link_id_from_discord(&pool, user_id.unwrap_or(ctx.author().id.get())).await;
let t = match link_id {
Some(id) => minecraft_uuids(&pool, id).await,
None => Vec::new(),
};
let mut content = format!("## {}'s linked accounts: ", user_name.unwrap_or(author_name));
for l in t {
content.push_str(format!("\nuuid: {}", l).as_str())
}
let reply = CreateReply::default().ephemeral(true).content(content);
if let Err(why) = ctx.send(reply).await {
println!("Error sending message: {why}");
}
Ok(())
}
async fn link(discord_id: u64, uuid: &str, pool: &Pool<Sqlite>) {
let link_id = match link_id_from_minecraft(pool, uuid.to_string()).await {
None => new_link_id(pool).await,
Some(link_id_mc_old) => {
// merge sets
let new_link_id_discord = link_id_from_discord(pool, discord_id)
.await
.unwrap_or(u16::MAX)
.cast_signed();
let _ = sqlx::query(format!("UPDATE minecraft_links SET link_id = {} WHERE link_id = {new_link_id_discord};", link_id_mc_old.cast_signed()).as_str()).execute(pool).await;
let _ = sqlx::query(
format!(
"UPDATE discord_links SET link_id = {} WHERE link_id = {new_link_id_discord};",
link_id_mc_old.cast_signed()
)
.as_str(),
)
.execute(pool)
.await;
link_id_mc_old
}
};
let link_id = link_id.cast_signed();
let discord_id = discord_id.cast_signed();
let _ = sqlx::query(
format!("INSERT INTO minecraft_links VALUES ({link_id}, \"{uuid}\");").as_str(),
)
.execute(pool)
.await;
let _ = sqlx::query(
format!("INSERT INTO discord_links VALUES ({link_id}, \"{discord_id}\");").as_str(),
)
.execute(pool)
.await;
}
#[derive(Serialize, Deserialize)]
struct Links {
#[serde(rename = "DISCORD")]
pub discord: String,
}
#[derive(Serialize, Deserialize)]
struct SocialMedia {
pub links: Links,
pub prompt: bool,
}
#[derive(Serialize, Deserialize)]
struct HypixelPlayer {
#[serde(rename = "socialMedia")]
pub social_media: SocialMedia,
}
#[derive(Serialize, Deserialize)]
struct HypixelResponse {
#[serde(rename = "player")]
pub player: HypixelPlayer,
}
#[derive(Serialize, Deserialize)]
struct MojangPlayer {
pub id: String,
pub name: String,
}
async fn minecraft_uuid_for_username(name: String) -> Result<String, serde_json::Error> {
let url = format!("https://api.mojang.com/users/profiles/minecraft/{name}");
let response = reqwest::get(url).await.unwrap();
let response_text = response.text().await.unwrap();
return (serde_json::from_str(response_text.as_str())
as Result<MojangPlayer, serde_json::Error>)
.map(|mojang_player: MojangPlayer| mojang_player.id);
}
async fn linked_discord_for_uuid(
hypixel_client: reqwest::Client,
uuid: &str,
) -> Result<String, Error> {
let hypixel_url = format!("https://api.hypixel.net/v2/player?uuid={uuid}");
return match hypixel_client.get(hypixel_url).send().await {
Ok(response) => {
let response_text = response.text().await.unwrap();
match (serde_json::from_str(response_text.as_str())
as Result<HypixelResponse, serde_json::Error>)
.map(|hypixel_response: HypixelResponse| {
hypixel_response.player.social_media.links.discord
}) {
Ok(discord) => Ok(discord),
Err(why) => Err(Error::try_from(why).unwrap()),
}
}
Err(why) => Err(Error::try_from(why).unwrap()),
};
}
#[derive(sqlx::FromRow)]
struct DiscordLink {
link_id: i16,
discord_id: i64,
}
#[derive(sqlx::FromRow)]
struct MinecraftLink {
link_id: i16,
minecraft_uuid: String,
}
#[derive(sqlx::FromRow)]
struct LinkId {
link_id: i16,
}
async fn link_id_from_discord(pool: &Pool<Sqlite>, snowflake: u64) -> Option<u16> {
let discord_id: i64 = snowflake.cast_signed();
return query_as(
format!("SELECT * FROM discord_links WHERE discord_id = {discord_id} LIMIT 1;").as_str(),
)
.fetch_optional(pool)
.await
.unwrap()
.map(|discord_link: DiscordLink| discord_link.link_id.cast_unsigned());
}
async fn link_id_from_minecraft(pool: &Pool<Sqlite>, minecraft_uuid: String) -> Option<u16> {
return query_as(
format!(
"SELECT * FROM minecraft_links WHERE minecraft_uuid = \"{minecraft_uuid}\" LIMIT 1;"
)
.as_str(),
)
.fetch_optional(pool)
.await
.unwrap()
.map(|minecraft_link: MinecraftLink| minecraft_link.link_id.cast_unsigned());
}
async fn new_link_id(pool: &Pool<Sqlite>) -> u16 {
let result: Result<LinkId, sqlx::Error> = query_as("SELECT link_id FROM minecraft_links WHERE link_id = (SELECT MAX(link_id) FROM minecraft_links) LIMIT 1;")
.fetch_one(pool)
.await;
result.unwrap().link_id.cast_unsigned() + 1
}
async fn minecraft_uuids(pool: &Pool<Sqlite>, link_id: u16) -> Vec<String> {
let link_id: i16 = link_id.cast_signed();
let link_result: Result<Vec<MinecraftLink>, sqlx::Error> =
query_as(format!("SELECT * FROM minecraft_links WHERE link_id = {link_id};").as_str())
.fetch_all(pool)
.await;
return match link_result {
Ok(links) => links
.into_iter()
.map(|minecraft_link: MinecraftLink| minecraft_link.minecraft_uuid)
.collect(),
Err(why) => {
println!("Error: {}", why);
Vec::new()
}
};
}
/*
async fn discord_ids(pool: &Pool<Sqlite>, link_id: u16) -> Vec<u64> {
let link_id: i16 = link_id.cast_signed();
let link_result: Result<Vec<DiscordLink>, sqlx::Error> = query_as(format!("SELECT * FROM discord_links WHERE link_id = {link_id}").as_str())
.fetch_all(pool)
.await;
return match link_result {
Ok(links) => links.into_iter().map(|discord_link: DiscordLink| discord_link.discord_id.cast_unsigned()).collect(),
Err(why) => {
println!("Error: {}", why);
Vec::new()
}
}
}*/

View file

@ -8,7 +8,7 @@ pub(crate) async fn send_simple(ctx: Context<'_>, reply: String) -> Result<(), E
content: Some(reply),
embeds: vec![],
attachments: vec![],
ephemeral: None,
ephemeral: Some(true),
components: None,
allowed_mentions: None,
reply: false,
@ -21,12 +21,12 @@ pub(crate) async fn send_simple(ctx: Context<'_>, reply: String) -> Result<(), E
Ok(())
}
pub(crate) fn cooldown(ctx: &Context, user: u64, global: u64) -> Result<(), Error> {
pub(crate) fn cooldown(ctx: &Context, user: u64, guild: u64) -> Result<(), Error> {
let mut cooldown_tracker = ctx.command().cooldowns.lock().unwrap();
let cooldown_durations = poise::CooldownConfig {
global: Some(Duration::from_secs(global)),
global: None,
user: Some(Duration::from_secs(user)),
guild: None,
guild: Some(Duration::from_secs(guild)),
channel: None,
member: None,
__non_exhaustive: (),

View file

@ -1,5 +1,5 @@
use poise::{ChoiceParameter, CreateReply};
use serenity::all::{CreateAllowedMentions, RoleId};
use serenity::all::{CreateAllowedMentions, GuildId, RoleId};
//from main.rs
use crate::commands::command_helper::cooldown;
@ -73,7 +73,7 @@ pub(crate) async fn lfg(
note: Option<String>,
) -> Result<(), Error> {
let mut reply: CreateReply = CreateReply::default();
let guild_id = ctx.guild_id().unwrap().get();
reply = match cooldown(&ctx, 600, 300) {
Ok(_) => {
let current: u8 = current_players.unwrap_or(1);
@ -82,7 +82,7 @@ pub(crate) async fn lfg(
desired = 4
}
let map_name: &str = map.name();
let ping: u64 = match mode {
let old_ping: u64 = match mode {
Casual => match map {
DeadEnd => 1005837123921915914,
BadBlood => 1140190470698438666,
@ -94,6 +94,23 @@ pub(crate) async fn lfg(
Event => 1175116511095050331,
//Tournament => 1210508966036242445,
};
let new_ping: u64 = match mode {
Casual => match map {
DeadEnd => 1257408106783178752,
BadBlood => 1257408198541836459,
AlienArcadium => 1257408233757343815,
Prison => 1257408303835644029,
},
Speedrun => 1257408362367287367,
Challenge => 1257408398631370762,
Event => 1257408432063905915,
//Tournament => 1210508966036242445,
};
let ping = match guild_id {
1256217633959841853 => new_ping,
995300932164276234 => old_ping,
_ => 0
};
let difficulty: Difficulty = match map {
DeadEnd | BadBlood | Prison => difficulty.unwrap_or(Normal),
AlienArcadium => Normal,
@ -169,6 +186,7 @@ pub(crate) async fn expert (
if current >= desired {
desired = 4
}
//TODO ZMPv2
let (ping, allowed_roles): (u64, Vec<u64>) = match mode {
Expert::Speedrun => (
1243676538067488828,

View file

@ -3,3 +3,4 @@ pub(crate) mod command_helper;
pub(crate) mod helpstart;
pub(crate) mod lfg;
pub(crate) mod xd;
pub(crate) mod account;

View file

@ -1,34 +1,51 @@
#![feature(integer_sign_cast)]
use std::collections::HashSet;
use std::convert::Into;
use std::future::Future;
use std::sync::Arc;
use std::time::Duration;
use poise::{async_trait, serenity_prelude as serenity};
use serenity::{client::EventHandler, FullEvent, model::id::UserId};
use serenity::all::ActivityData;
use serenity::all::{ActivityData, Attachment, ChannelId, CreateAttachment, CreateMessage, Event, Guild, GuildChannel};
use serenity::all::Route::Channel;
use sqlx::{Acquire, ConnectOptions, Executor, Sqlite};
use tokio::sync::RwLock;
mod commands;
struct Data {
bots: Arc<RwLock<u8>>
bots: Arc<RwLock<u8>>,
sqlite_pool: sqlx::Pool<Sqlite>,
hypixel_api_client: reqwest::Client
} // User data, which is stored and accessible in all command invocations
type Error = Box<dyn std::error::Error + Send + Sync>;
type Context<'a> = poise::Context<'a, Data, Error>;
struct ReadyHandler;
#[async_trait]
impl EventHandler for ReadyHandler {
async fn ready(
&self,
_: poise::serenity_prelude::Context,
ready: poise::serenity_prelude::Ready,
) {
println!("{} is connected!", ready.user.id);
}
}
#[tokio::main]
async fn main() {
let sqlite_pool = sqlx::sqlite::SqlitePoolOptions::new()
.idle_timeout(Duration::from_secs(10))
.max_connections(3)
.connect_lazy("sqlite:accounts.db").unwrap();
let hypixel_api: String = std::env::var("HYPIXEL_API_KEY").unwrap();
let hypixel_api_client = {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
"API-Key",
reqwest::header::HeaderValue::try_from(hypixel_api).unwrap(),
);
reqwest::ClientBuilder::new()
.default_headers(headers)
.build().unwrap()
};
let options = poise::FrameworkOptions {
commands: vec![
commands::lfg::lfg(),
@ -36,6 +53,7 @@ async fn main() {
commands::xd::xd(),
commands::helpstart::helpstart(),
commands::bots::bots(),
commands::account::account(),
],
manual_cooldowns: true,
prefix_options: poise::PrefixFrameworkOptions {
@ -63,6 +81,8 @@ async fn main() {
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
Ok(Data {
bots: Arc::new(RwLock::new(0)),
sqlite_pool,
hypixel_api_client
})
})
})
@ -87,7 +107,7 @@ async fn event_handler(
match event {
FullEvent::Ready { data_about_bot, .. } => {
println!("Logged in as {}", data_about_bot.user.name);
}
},
_ => {}
}
Ok(())