FrenBot/src/discord/shop.rs

523 lines
17 KiB
Rust

use crate::discord::get_role;
use crate::discord::motivate::create_motivation_image;
use crate::error::Error;
use crate::inventory::{nft_value, InventoryError, ItemData, ItemType, Operation};
use crate::models::motivation::MotivationConfig;
use crate::models::task::{Task, TaskType};
use crate::user::{User, UserError};
use crate::{command, group, GlobalData};
use rand::prelude::IndexedRandom;
use rand::{rng, Rng};
use serenity::all::{
parse_user_mention, CreateAttachment, CreateMessage, EditRole, GuildId, Member,
};
use serenity::client::Context;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity::model::Colour;
use serenity::prelude::Mentionable;
use serenity::utils::MessageBuilder;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hasher;
use tokio::io::AsyncWriteExt;
#[group]
#[commands(shop, buy, inventory, use_item, sell_item, item_help)]
pub struct Shop;
#[command]
#[description("Fren has wares if you have coin")]
async fn shop(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let bot_user = User::get_user(&global_data.db, ctx.cache.current_user().id)?;
if bot_user.inventory.inventory.is_empty() {
msg.reply(&ctx.http, "Sorry shop is closed until we get more wares.")
.await?;
} else {
let mut inv_msg = MessageBuilder::new();
inv_msg.push_bold_line("Fren has wares if you have coin:");
inv_msg.push_safe(bot_user.inventory.list_items(true));
msg.reply(&ctx.http, inv_msg.build()).await?;
}
Ok(())
}
#[command]
#[description("Open your inventory, it specifically can not be rebound to the B key")]
async fn inventory(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let mut inv_msg = MessageBuilder::new();
let user = User::get_user(&global_data.db, msg.author.id)?;
if user.inventory.inventory.is_empty() {
msg.reply(&ctx, "Sorry your inventory is empty.").await?;
} else {
inv_msg.push_bold_line("Your inventory: ");
inv_msg.push_safe(user.inventory.list_items(false));
msg.reply(&ctx.http, inv_msg.build()).await?;
}
Ok(())
}
#[command]
#[description("Buying something?")]
async fn buy(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
if args.is_empty() {
msg.reply(
&ctx.http,
"You gonna buy something or do you just like to stare?",
)
.await?;
return Ok(());
}
let item = match args.rest().parse::<ItemType>() {
Ok(i) => i,
Err(_) => {
msg.reply(&ctx.http, "I don't know what the heck that is tbh.")
.await?;
return Ok(());
}
};
let res = User::item_transaction(
&global_data.db,
ctx.cache.current_user().id,
msg.author.id,
item,
Operation::Buy,
);
if let Err(err) = res {
if let Error::UserError(err) = err {
let resp = match err {
UserError::NotEnoughFunds => "Sorry Link, but I can't give store credit. Come back when you are mhhmm richer.".to_string(),
UserError::InvalidTarget => "I don't know who you are or how you got here.".to_string(),
UserError::InventoryError(err) => format!("Something went wrong with that item: {}", err)
};
msg.reply(&ctx.http, resp).await?;
} else {
return Err(err.into());
}
} else {
msg.reply(&ctx, format!("Congrats, you now own a '{}'", item))
.await?;
}
Ok(())
}
async fn blockable_item(
ctx: &Context,
global_data: &GlobalData,
guild_id: GuildId,
msg_content: &str,
role_name: &str,
weapon_name: &str,
block_item: ItemType,
) -> CommandResult<(bool, Member)> {
let split = msg_content.split(weapon_name);
let target = parse_user_mention(split.last().unwrap()).unwrap();
let target_member = match guild_id.member(&ctx.http, target).await {
Ok(member) => member,
Err(_) => return Err(CommandError::from("I have no clue who that is tbh")),
};
if target_member.user.id == ctx.cache.current_user().id {
return Err(CommandError::from(
"You can not harm me in a way that matters.",
));
}
if User::try_use_item(&global_data.db, target, block_item, true).is_ok() {
Ok((false, target_member))
} else {
let role = if let Some(role) = get_role(&ctx.http, guild_id, role_name).await {
role
} else {
guild_id
.create_role(
&ctx.http,
EditRole::new()
.name(role_name)
.colour(Colour::from_rgb(1, 1, 1)),
)
.await
.unwrap()
.id
};
target_member.add_role(&ctx.http, role).await?;
Task::add_task(
&global_data.db,
TaskType::RemoveRole {
role_id: role.get(),
user_id: target_member.user.id.get(),
},
chrono::Utc::now() + chrono::Duration::seconds(global_data.cfg.effect_role_duration),
)?;
Ok((true, target_member))
}
}
#[command]
#[only_in(guilds)]
#[aliases("use")]
#[description("Use it or lose it")]
async fn use_item(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
if args.is_empty() {
msg.reply(&ctx.http, "You need to select one item to use.")
.await?;
return Ok(());
}
let msg_content = args.rest().to_lowercase();
let item = match msg_content.parse::<ItemType>() {
Ok(i) => i,
Err(_) => {
msg.reply(&ctx.http, "I don't know what the heck that is tbh.")
.await?;
return Ok(());
}
};
let item_data = match User::try_use_item(&global_data.db, msg.author.id, item, false) {
Ok(i) => i,
Err(err) => {
if let Error::UserError(UserError::InventoryError(InventoryError::NotEnoughItems)) = err
{
msg.reply(
&ctx.http,
"Looks like you don't have enough of that item to use it",
)
.await?;
}
return Ok(());
}
};
match item {
ItemType::CancelInsurance => {
msg.reply(&ctx.http, "You are immune to the next (1) cancelings.")
.await?;
}
ItemType::TheConceptOfLove => {
msg.reply(&ctx.http, "I have DMed you the concept of love.")
.await?;
msg.author
.id
.create_dm_channel(&ctx.http)
.await?
.say(
&ctx.http,
"DO NOT SHARE\nhttps://www.youtube.com/watch?v=HNy_retSME0",
)
.await?;
}
ItemType::GoodFortune => {
let good_fortunes = [
"Yes.",
"OF COURSE.",
"Carolyn, I'm sorry. So yes :)",
"That sounds great!",
"Yes, I am happy for you!",
"||YES||",
];
let fortune = good_fortunes.choose(&mut rng()).unwrap();
msg.reply(&ctx.http, fortune.to_string()).await?;
}
ItemType::Nft => {
if let Some(ItemData::Nft(path)) = item_data {
let file: tokio::fs::File = match tokio::fs::File::open(&path).await {
Ok(f) => f,
Err(_) => {
msg.reply(&ctx.http, "Sorry this was a pump and dump")
.await?;
return Ok(());
}
};
let value = nft_value(&path);
msg.channel_id
.send_message(
&ctx.http,
CreateMessage::new()
.content(format!(
"Your NFT my good friend. It's worth **{} FC**!",
value
))
.add_file(
CreateAttachment::file(&file, "nft.jpg".to_string())
.await
.unwrap(),
),
)
.await?;
} else {
msg.reply(
&ctx.http,
"Fren NFTs were never my creation, I merely promoted the brand",
)
.await?;
}
}
ItemType::LicenseToBeHorny => {
msg.reply(&ctx.http, "https://media.discordapp.net/attachments/840015650286075945/1127022083919069184/Img_2022_10_21_05_08_12.jpg").await?;
}
ItemType::KillGun => {
let (outcome, target) = match blockable_item(
ctx,
global_data,
msg.guild_id.unwrap(),
&msg_content,
"Dead",
"kill gun ",
ItemType::Helmet,
)
.await
{
Ok(ret) => ret,
Err(err) => {
msg.reply(&ctx.http, err.to_string()).await?;
return Ok(());
}
};
if outcome {
msg.reply(&ctx.http, format!("You draw your trusty kill gun and shoot one kill bullet. It hits it mark between {}'s eyes, killing them instantly. They are now dead.", target.mention())).await?;
} else {
msg.reply(&ctx.http, format!("The kill bullet shoots at kill velocity toward {}! They smirk, and simply pull out their Helmet and put it on, the bullet bounces off and falls to the floor. The crowd gasps (like they do in my animes). \"No death today pal\"", target.mention())).await?;
}
}
ItemType::CancelRay => {
let (outcome, target) = match blockable_item(
ctx,
global_data,
msg.guild_id.unwrap(),
&msg_content,
"Cancelled",
"cancel ray ",
ItemType::CancelInsurance,
)
.await
{
Ok(ret) => ret,
Err(err) => {
msg.reply(&ctx.http, err.to_string()).await?;
return Ok(());
}
};
if outcome {
msg.reply(&ctx.http, format!("You shoot the cancel ray at {}. As the ray impacts them, you can here their phone buzz. They have been cancelled, you hear the liberal media in the distance.", target.mention())).await?;
} else {
msg.reply(&ctx.http, format!("The ray nearly hits {}, but they are surrounded in a a shimmering blue energy shield. \"The liberal media won't strike this time!\"", target.mention())).await?;
}
}
ItemType::Helmet => {
msg.reply(&ctx.http, "You're trusty helmet regards you helmetly")
.await?;
}
ItemType::TacticalNuke => {
let mut users: Vec<User> = global_data.db.filter(|_, _user: &User| true)?.collect();
for user in &mut users {
for item in &mut user.inventory.inventory {
if item.item_type == ItemType::KillGun
|| item.item_type == ItemType::CancelRay
|| item.item_type == ItemType::CancelInsurance
|| item.item_type == ItemType::Helmet
{
item.quantity = 0;
}
}
global_data.db.insert(user.clone())?;
}
msg.reply(&ctx.http, "I have become cancel, the canceller of worlds. https://tenor.com/view/explosion-mushroom-cloud-atomic-bomb-bomb-boom-gif-4464831").await?;
}
}
Ok(())
}
#[command]
#[only_in(guilds)]
#[aliases("sell")]
#[description("Sell it or smell it")]
async fn sell_item(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let item = match args.rest().parse::<ItemType>() {
Ok(i) => i,
Err(_) => {
msg.reply(&ctx.http, "I don't know what the heck that is tbh.")
.await?;
return Ok(());
}
};
let bot_user = ctx.cache.current_user().id;
match User::item_transaction(
&global_data.db,
msg.author.id,
bot_user,
item,
Operation::Sell,
) {
Ok(_) => {
msg.reply(&ctx.http, "I guess I could buy that...").await?;
}
Err(err) => {
if let Error::UserError(err) = err {
match err {
UserError::NotEnoughFunds => {
msg.reply(&ctx.http, "What do I look like a charity service?")
.await?;
}
UserError::InvalidTarget => {
msg.reply(&ctx.http, "If you got here, that means I'm not real...????")
.await?;
}
UserError::InventoryError(InventoryError::NotEnoughItems) => {
msg.reply(
&ctx.http,
"You don't have enough of that item to sell it lmao",
)
.await?;
}
_ => {}
}
} else {
return Err(err.into());
}
}
}
Ok(())
}
#[command]
#[only_in(guilds)]
#[description("Get help with an item")]
async fn item_help(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let item = match args.rest().parse::<ItemType>() {
Ok(i) => i,
Err(_) => {
msg.reply(&ctx.http, "I don't know what the heck that is tbh.")
.await?;
return Ok(());
}
};
msg.reply(&ctx.http, format!("**{}**: {}", item, item.description()))
.await?;
Ok(())
}
pub async fn restock_shop(ctx: &Context, global_data: &GlobalData) -> Result<(), CommandError> {
let mut bot_user = User::get_user(&global_data.db, ctx.cache.current_user().id)?;
bot_user.inventory.inventory.clear();
bot_user
.inventory
.give_item(ItemType::TheConceptOfLove, rng().gen_range(0..=5), None);
bot_user
.inventory
.give_item(ItemType::GoodFortune, rng().gen_range(0..10), None);
bot_user
.inventory
.give_item(ItemType::CancelInsurance, rng().gen_range(1..=3), None);
bot_user
.inventory
.give_item(ItemType::LicenseToBeHorny, rng().gen_range(0..=25), None);
bot_user
.inventory
.give_item(ItemType::KillGun, rng().gen_range(1..=3), None);
bot_user
.inventory
.give_item(ItemType::CancelRay, rng().gen_range(1..=3), None);
bot_user
.inventory
.give_item(ItemType::Helmet, rng().gen_range(1..=3), None);
bot_user
.inventory
.give_item(ItemType::TacticalNuke, rng().gen_range(0..=1), None);
loop {
let mut dir = tokio::fs::read_dir(&global_data.cfg.nft_path).await?;
let mut count = 0;
while let Ok(Some(_)) = dir.next_entry().await {
count += 1;
}
if count > 64 {
tokio::fs::remove_dir_all(&global_data.cfg.nft_path).await?;
tokio::fs::create_dir(&global_data.cfg.nft_path).await?
}
let nft_motivation = MotivationConfig::generate_motivation(
&global_data.db,
&global_data.picox,
"gold",
None,
)
.await
.unwrap();
let nft = create_motivation_image(nft_motivation).await.unwrap();
let mut hasher = DefaultHasher::new();
hasher.write(&nft);
let nft_hash = hasher.finish();
let path = global_data.cfg.nft_path.join(format!("{}.jpg", nft_hash));
if path.exists() {
continue;
}
let mut file = tokio::fs::File::create(path.clone()).await?;
file.write_all(&nft).await?;
bot_user.inventory.give_item(
ItemType::Nft,
1,
Some(ItemData::Nft(path.to_str().unwrap().to_string())),
);
break;
}
global_data.db.insert::<User>(bot_user)?;
Ok(())
}