Add back in lil fren commands, delete story module

This commit is contained in:
Joey Hines 2025-03-22 18:59:14 -06:00
parent 3005178a62
commit 8b9d1da206
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
4 changed files with 124 additions and 367 deletions

View File

@ -1,68 +1,45 @@
use crate::config::GlobalData; use crate::discord::Context;
use crate::error::Error;
use crate::models::lil_fren; use crate::models::lil_fren;
use crate::models::lil_fren::{AliveState, LilFren, LilFrenState}; use crate::models::lil_fren::{AliveState, LilFren, LilFrenState};
use crate::{command, group}; use poise::serenity_prelude::model::misc::EmojiIdentifier;
use poise::serenity_prelude::utils::MessageBuilder;
use rand::prelude::IndexedRandom; use rand::prelude::IndexedRandom;
use rand::{rng, Rng}; use rand::{rng, Rng};
use serenity::client::Context;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use serenity::model::misc::EmojiIdentifier;
use serenity::utils::MessageBuilder;
#[group] #[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
#[commands(adopt, checkup, feed, give_water, play, give_medicine)] pub async fn adopt(
pub struct Buddy; ctx: Context<'_>,
#[description = "Lil buddy to adopt"] lil_buddy: EmojiIdentifier,
#[command] ) -> Result<(), Error> {
#[description("Adopt a new lil buddy")] if let Some(lil_fren) = LilFren::get_lil_fren(&ctx.data().db)? {
#[only_in(guilds)]
async fn adopt(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
if let Ok(emoji) = args.parse::<EmojiIdentifier>() {
if let Some(lil_fren) = LilFren::get_lil_fren(&global_data.db)? {
if lil_fren.is_alive() == AliveState::Alive { if lil_fren.is_alive() == AliveState::Alive {
msg.reply( ctx.reply("Your buddy is still alive, please take care of him :)")
&ctx.http,
"Your buddy is still alive, please take care of him :)",
)
.await?; .await?;
return Ok(()); return Ok(());
} }
} }
LilFren::create_new_lil_fren(&global_data.db, emoji.id)?; LilFren::create_new_lil_fren(&ctx.data().db, lil_buddy.id)?;
let guild = msg.guild_id.unwrap(); let guild = ctx.guild_id().unwrap();
let emoji = guild.emoji(&ctx.http, emoji.id).await?; let emoji = guild.emoji(&ctx, lil_buddy.id).await?;
let mut msg_builder = MessageBuilder::new(); let mut msg_builder = MessageBuilder::new();
msg_builder.push("Congrats on adopting "); msg_builder.push("Congrats on adopting ");
msg_builder.emoji(&emoji); msg_builder.emoji(&emoji);
msg_builder.push("!"); msg_builder.push("!");
ctx.reply(msg_builder.build()).await?;
msg.reply(&ctx.http, msg_builder.build()).await?;
} else {
msg.reply(&ctx.http, "Sorry whatever that is not adoptable.")
.await?;
}
Ok(()) Ok(())
} }
#[command] #[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
#[only_in(guilds)] pub async fn checkup(ctx: Context<'_>) -> Result<(), Error> {
#[description("Checkup on your lil buddy")] let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
async fn checkup(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let lil_fren = LilFren::get_lil_fren(&global_data.db)?;
if let Some(lil_fren) = lil_fren { if let Some(lil_fren) = lil_fren {
let guild = msg.guild_id.unwrap(); let guild = ctx.guild_id().unwrap();
let alive_state = lil_fren.is_alive(); let alive_state = lil_fren.is_alive();
if alive_state == AliveState::Alive { if alive_state == AliveState::Alive {
@ -85,9 +62,8 @@ async fn checkup(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
} }
}; };
msg.reply(&ctx.http, state_msg).await?; ctx.reply(state_msg).await?;
msg.reply(&ctx.http, lil_fren.draw(ctx, &guild).await) ctx.reply(lil_fren.draw(ctx, &guild).await).await?;
.await?;
} else { } else {
let resp = match alive_state { let resp = match alive_state {
AliveState::Alive => "", AliveState::Alive => "",
@ -96,142 +72,116 @@ async fn checkup(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
AliveState::DiedOfThirst => "Your little buddy died of thirst", AliveState::DiedOfThirst => "Your little buddy died of thirst",
}; };
msg.reply(&ctx.http, resp).await?; ctx.reply(resp).await?;
} }
} else { } else {
msg.reply( ctx.reply("Sorry you have no little buddy right now. Please adopt!")
&ctx.http,
"Sorry you have no little buddy right now. Please adopt!",
)
.await?; .await?;
} }
Ok(()) Ok(())
} }
#[command] #[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
#[description("Feed lil buddy")] pub async fn feed(
async fn feed(ctx: &Context, msg: &Message, args: Args) -> CommandResult { ctx: Context<'_>,
let mut data = ctx.data.write().await; #[description = "Item to feed lil buddy"] food: String,
let global_data = data.get_mut::<GlobalData>().unwrap(); ) -> Result<(), Error> {
let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
if let Ok(food_emoji) = args.parse::<String>() {
let lil_fren = LilFren::get_lil_fren(&global_data.db)?;
if let Some(mut lil_fren) = lil_fren { if let Some(mut lil_fren) = lil_fren {
if lil_fren.is_alive() == AliveState::Alive { if lil_fren.is_alive() == AliveState::Alive {
let feed: f32 = rng().gen_range(-0.1..=1.0); let feed: f32 = rng().gen_range(-0.1..=1.0);
lil_fren.hunger = (lil_fren.hunger + feed).clamp(-1.0, 1.0); lil_fren.hunger = (lil_fren.hunger + feed).clamp(-1.0, 1.0);
let guild = msg.guild_id.unwrap(); let guild = ctx.guild_id().unwrap();
let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; let lil_fren_emoji = guild.emoji(&ctx, lil_fren.emoji).await?;
if feed > 0.0 { if feed > 0.0 {
msg.reply(&ctx.http, "Lil buddy seemed to like that!") ctx.reply("Lil buddy seemed to like that!").await?;
.await?;
} else { } else {
msg.reply(&ctx.http, "Lil buddy DID not like that!").await?; ctx.reply("Lil buddy DID not like that!").await?;
} }
msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, &food_emoji)) ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, &food))
.await?; .await?;
global_data.db.insert(lil_fren)?; ctx.data().db.insert(lil_fren)?;
} else { } else {
msg.reply(&ctx.http, "You may want to check on your buddy...") ctx.reply("You may want to check on your buddy...").await?;
.await?;
} }
} else { } else {
msg.reply(&ctx.http, "Sorry no little buddy found!").await?; ctx.reply("Sorry no little buddy found!").await?;
}
} else {
msg.reply(&ctx.http, "Please provide something to feed lil buddy")
.await?;
} }
Ok(()) Ok(())
} }
#[command] #[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
#[description("Give lil buddy some water")] pub async fn give_water(ctx: Context<'_>) -> Result<(), Error> {
async fn give_water(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let lil_fren = LilFren::get_lil_fren(&global_data.db)?;
if let Some(mut lil_fren) = lil_fren { if let Some(mut lil_fren) = lil_fren {
if lil_fren.is_alive() == AliveState::Alive { if lil_fren.is_alive() == AliveState::Alive {
lil_fren.thirst = (lil_fren.thirst + 0.5).clamp(-1.0, 1.0); lil_fren.thirst = (lil_fren.thirst + 0.5).clamp(-1.0, 1.0);
let guild = msg.guild_id.unwrap(); let guild = ctx.guild_id().unwrap();
let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; let lil_fren_emoji = guild.emoji(ctx, lil_fren.emoji).await?;
msg.reply(&ctx.http, "Lil buddy be drinking!").await?; ctx.reply("Lil buddy be drinking!").await?;
global_data.db.insert(lil_fren)?; ctx.data().db.insert(lil_fren)?;
let drink = if rng().gen_bool(0.20) { let drink = if rng().random_bool(0.20) {
":beer:" ":beer:"
} else { } else {
":cup_with_straw:" ":cup_with_straw:"
}; };
msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, drink)) ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, drink))
.await?; .await?;
} else { } else {
msg.reply(&ctx.http, "You may want to check on your buddy...") ctx.reply("You may want to check on your buddy...").await?;
.await?;
} }
} else { } else {
msg.reply(&ctx.http, "Sorry no little buddy found!").await?; ctx.reply("Sorry no little buddy found!").await?;
} }
Ok(()) Ok(())
} }
#[command] #[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
#[description("Play with buddy")] pub async fn play(ctx: Context<'_>) -> Result<(), Error> {
async fn play(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let lil_fren = LilFren::get_lil_fren(&global_data.db)?;
if let Some(mut lil_fren) = lil_fren { if let Some(mut lil_fren) = lil_fren {
if lil_fren.is_alive() == AliveState::Alive { if lil_fren.is_alive() == AliveState::Alive {
lil_fren.entertainment = lil_fren.entertainment =
(lil_fren.entertainment + rng().gen_range(0.10..1.0)).clamp(-1.0, 1.0); (lil_fren.entertainment + rng().random_range(0.10..1.0)).clamp(-1.0, 1.0);
let guild = msg.guild_id.unwrap(); let guild = ctx.guild_id().unwrap();
let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; let lil_fren_emoji = guild.emoji(ctx, lil_fren.emoji).await?;
global_data.db.insert(lil_fren)?; ctx.data().db.insert(lil_fren)?;
let toy = global_data.cfg.toys.choose(&mut rng()).unwrap(); let toy = ctx.data().cfg.toys.choose(&mut rng()).unwrap();
msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, toy)) ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, toy)).await?;
.await?;
} else { } else {
msg.reply(&ctx.http, "You may want to check on your buddy...") ctx.reply("You may want to check on your buddy...").await?;
.await?;
} }
} else { } else {
msg.reply(&ctx.http, "Sorry no little buddy found!").await?; ctx.reply("Sorry no little buddy found!").await?;
} }
Ok(()) Ok(())
} }
#[command] #[poise::command(prefix_command, guild_only, category = "Lil Buddy")]
#[description("Help heal buddy")] pub async fn give_medicine(ctx: Context<'_>) -> Result<(), Error> {
async fn give_medicine(ctx: &Context, msg: &Message, _args: Args) -> CommandResult { let lil_fren = LilFren::get_lil_fren(&ctx.data().db)?;
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let lil_fren = LilFren::get_lil_fren(&global_data.db)?;
if let Some(mut lil_fren) = lil_fren { if let Some(mut lil_fren) = lil_fren {
if lil_fren.is_alive() == AliveState::Alive { if lil_fren.is_alive() == AliveState::Alive {
let guild = msg.guild_id.unwrap(); let guild = ctx.guild_id().unwrap();
let reply = if lil_fren.state == LilFrenState::Sick { let reply = if lil_fren.state == LilFrenState::Sick {
lil_fren.state = LilFrenState::Standing; lil_fren.state = LilFrenState::Standing;
@ -240,19 +190,18 @@ async fn give_medicine(ctx: &Context, msg: &Message, _args: Args) -> CommandResu
"Little buddy is now addicted to cough drops, I hope you are happy" "Little buddy is now addicted to cough drops, I hope you are happy"
}; };
let lil_fren_emoji = guild.emoji(&ctx.http, lil_fren.emoji).await?; let lil_fren_emoji = guild.emoji(ctx, lil_fren.emoji).await?;
global_data.db.insert(lil_fren)?; ctx.data().db.insert(lil_fren)?;
msg.reply(&ctx.http, reply).await?; ctx.reply(reply).await?;
msg.reply(&ctx.http, lil_fren::draw_feed(&lil_fren_emoji, ":pill:")) ctx.reply(lil_fren::draw_feed(&lil_fren_emoji, ":pill:"))
.await?; .await?;
} else { } else {
msg.reply(&ctx.http, "You may want to check on your buddy...") ctx.reply("You may want to check on your buddy...").await?;
.await?;
} }
} else { } else {
msg.reply(&ctx.http, "Sorry no little buddy found!").await?; ctx.reply("Sorry no little buddy found!").await?;
} }
Ok(()) Ok(())

View File

@ -6,6 +6,7 @@ mod color;
mod emoji_race; mod emoji_race;
mod fren_coin; mod fren_coin;
mod joke; mod joke;
mod little_fren;
use crate::config::GlobalData; use crate::config::GlobalData;
use crate::error::Error; use crate::error::Error;
@ -15,6 +16,7 @@ use std::time::Duration;
use crate::discord::fren_coin::give_coin; use crate::discord::fren_coin::give_coin;
use crate::discord::joke::random; use crate::discord::joke::random;
use crate::models::lil_fren::lil_fren_task;
use crate::models::task::Task; use crate::models::task::Task;
use poise::serenity_prelude::{Message, ReactionType}; use poise::serenity_prelude::{Message, ReactionType};
use poise::{find_command, serenity_prelude as serenity, FrameworkOptions}; use poise::{find_command, serenity_prelude as serenity, FrameworkOptions};
@ -34,6 +36,7 @@ async fn event_handler(
info!("Bot ready, and logged in as {}", data_about_bot.user.name); info!("Bot ready, and logged in as {}", data_about_bot.user.name);
{ {
info!("Starting tasks handler...");
let data = data.clone(); let data = data.clone();
let ctx = ctx.clone(); let ctx = ctx.clone();
tokio::spawn(async move { tokio::spawn(async move {
@ -44,6 +47,12 @@ async fn event_handler(
} }
}); });
} }
{
info!("Starting lil buddy task...");
let data = data.clone();
tokio::spawn(async move { lil_fren_task(data) });
}
} }
serenity::FullEvent::Message { new_message } => { serenity::FullEvent::Message { new_message } => {
if new_message.content.starts_with("!") { if new_message.content.starts_with("!") {
@ -186,6 +195,12 @@ pub async fn run_bot(global_data: GlobalData) {
joke::list_random(), joke::list_random(),
joke::real_roll(), joke::real_roll(),
joke::roll(), joke::roll(),
little_fren::adopt(),
little_fren::checkup(),
little_fren::feed(),
little_fren::give_medicine(),
little_fren::give_water(),
little_fren::play(),
], ],
event_handler: |ctx, event, framework, data| { event_handler: |ctx, event, framework, data| {
Box::pin(event_handler(ctx, event, framework, data)) Box::pin(event_handler(ctx, event, framework, data))

View File

@ -1,207 +0,0 @@
use crate::config::{BotConfig, Channel};
use crate::{command, group, GlobalData};
use rand::prelude::{IndexedRandom, SliceRandom};
use rand::thread_rng;
use regex::Regex;
use serenity::client::Context;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::{Attachment, Message};
use serenity::utils::MessageBuilder;
use std::collections::HashMap;
use std::path::PathBuf;
#[group]
#[commands(story, word, list_stories, upload_story)]
pub struct Story;
async fn get_all_stories(cfg: &BotConfig) -> Vec<PathBuf> {
let mut dir = tokio::fs::read_dir(&cfg.story_path).await.unwrap();
let mut stories = Vec::new();
while let Some(file) = &dir.next_entry().await.unwrap() {
stories.push(file.path());
}
stories
}
fn get_all_blanks(story: &str) -> Vec<String> {
let re = Regex::new(r"\{%(?P<global>.*?)%}").unwrap();
let mut globals: Vec<String> = Vec::new();
for cap in re.captures_iter(story) {
if let Some(global) = cap.name("global") {
if !globals.contains(&global.as_str().to_string()) {
globals.push(global.as_str().to_string())
}
}
}
globals
}
#[command]
#[only_in(guilds)]
#[description("List Stories")]
async fn list_stories(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap();
let stories = get_all_stories(&global_data.cfg).await;
let mut resp = MessageBuilder::default();
resp.push_line("**Stories I know:**");
for story in stories {
resp.push_line(
story
.file_stem()
.unwrap()
.to_str()
.unwrap()
.replace('_', " "),
);
}
msg.reply(&ctx.http, resp.build()).await?;
Ok(())
}
#[command]
#[only_in(guilds)]
#[description("Let me tell you a tail")]
async fn story(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let story_channel = {
let data = ctx.data.read().await;
data.get::<Channel<String>>().unwrap().clone()
};
let mut story_recv = match story_channel.recv.try_lock() {
Ok(story_recv) => story_recv,
Err(_) => {
msg.reply(&ctx.http, "Let me finish this story bub").await?;
return Ok(());
}
};
let stories = {
let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap();
get_all_stories(&global_data.cfg).await
};
let story_path = if args.is_empty() {
let mut rng = thread_rng();
stories.choose(&mut rng)
} else {
let story_name = args.rest().to_lowercase().replace(' ', "_");
stories.iter().find(|story_p| {
let story_p_name = story_p.file_stem().unwrap().to_str().unwrap();
story_p_name == story_name
})
};
let story_path = match story_path {
None => {
msg.reply(&ctx.http, "No story found :(").await?;
return Ok(());
}
Some(story) => story,
};
let mut story_contents = tokio::fs::read_to_string(story_path).await?;
let mut story_globals: HashMap<String, String> = HashMap::new();
let msg_channel = msg.channel(&ctx.http).await?.id();
for global in get_all_blanks(&story_contents) {
msg_channel
.say(&ctx.http, format!("Give me {}", global))
.await?;
story_globals.insert(global, story_recv.recv().await.unwrap());
}
for (prompt, response) in story_globals {
story_contents = story_contents.replace(&format!("{{%{}%}}", prompt), &response)
}
let mut msg_builder = MessageBuilder::default();
for part in story_contents.split('\n') {
if (msg_builder.0.len() + part.len()) > serenity::constants::MESSAGE_CODE_LIMIT {
msg_channel.say(&ctx.http, msg_builder.build()).await?;
msg_builder = MessageBuilder::default();
}
msg_builder.push_line(part);
}
if !msg_builder.0.is_empty() {
msg_channel.say(&ctx.http, msg_builder.build()).await?;
}
Ok(())
}
#[command]
#[only_in(guilds)]
#[description("Give me a word")]
async fn word(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
let data = ctx.data.read().await;
let story_channel = data.get::<Channel<String>>().unwrap();
if story_channel.recv.try_lock().is_ok() {
msg.reply(&ctx.http, "No story in progress!").await?;
return Ok(());
}
let story_send = story_channel.send.lock().await;
let resp = MessageBuilder::default().push_safe(args.rest()).build();
story_send.send(resp).await?;
Ok(())
}
#[command]
#[description("Attach stories as .txt files")]
async fn upload_story(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
let data = ctx.data.read().await;
let global = data.get::<GlobalData>().unwrap();
let stories: Vec<&Attachment> = msg
.attachments
.iter()
.filter(|f| f.filename.ends_with(".txt"))
.collect();
for story in stories {
let story_txt = String::from_utf8(story.download().await?)?;
let globals = get_all_blanks(&story_txt);
let mut resp = MessageBuilder::new();
resp.push_line(format!("{} contains the following blanks:", story.filename));
for blank in globals {
resp.push_line(format!("* {}", blank));
}
msg.reply(&ctx.http, resp.build()).await?;
let story_path = global.cfg.story_path.join(&story.filename);
tokio::fs::write(&story_path, story_txt).await?;
}
Ok(())
}

View File

@ -1,14 +1,16 @@
use crate::config::GlobalData; use crate::config::GlobalData;
use crate::discord::Context;
use crate::error::Error; use crate::error::Error;
use j_db::database::Database; use j_db::database::Database;
use j_db::model::JdbModel; use j_db::model::JdbModel;
use log::{debug, info};
use poise::serenity_prelude::model::guild::Emoji; use poise::serenity_prelude::model::guild::Emoji;
use poise::serenity_prelude::model::id::{EmojiId, GuildId}; use poise::serenity_prelude::model::id::{EmojiId, GuildId};
use poise::serenity_prelude::prelude::Context;
use poise::serenity_prelude::utils::MessageBuilder; use poise::serenity_prelude::utils::MessageBuilder;
use rand::distr::{Distribution, StandardUniform}; use rand::distr::{Distribution, StandardUniform};
use rand::{rng, Rng}; use rand::{rng, Rng};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
pub fn draw_standing(emoji: &Emoji) -> String { pub fn draw_standing(emoji: &Emoji) -> String {
@ -296,8 +298,8 @@ impl LilFren {
lil_fren.thirst = lil_fren.thirst =
(lil_fren.entertainment + entertainment_diff * self.smarts).clamp(-1.0, 1.0); (lil_fren.entertainment + entertainment_diff * self.smarts).clamp(-1.0, 1.0);
if rng().gen_bool(0.75) { if rng().random_bool(0.75) {
println!("fren is now {:?}", self.state); info!("fren is now {:?}", self.state);
lil_fren.state = rng().random(); lil_fren.state = rng().random();
} }
@ -334,8 +336,8 @@ impl LilFren {
} }
} }
pub async fn draw(&self, ctx: &Context, guild: &GuildId) -> String { pub async fn draw(&self, ctx: Context<'_>, guild: &GuildId) -> String {
let emoji = guild.emoji(&ctx.http, self.emoji).await.unwrap(); let emoji = guild.emoji(&ctx, self.emoji).await.unwrap();
let alive_state = self.is_alive(); let alive_state = self.is_alive();
if let AliveState::Alive = alive_state { if let AliveState::Alive = alive_state {
@ -346,14 +348,12 @@ impl LilFren {
} }
} }
pub async fn lil_fren_task(ctx: &Context) { pub async fn lil_fren_task(data: Arc<GlobalData>) {
tokio::time::sleep(Duration::from_secs(60)).await; tokio::time::sleep(Duration::from_secs(60)).await;
let data = ctx.data.read().await; let fren = LilFren::get_lil_fren(&data.db).unwrap();
let global_data = data.get::<GlobalData>().unwrap();
let fren = LilFren::get_lil_fren(&global_data.db).unwrap();
if let Some(mut fren) = fren { if let Some(mut fren) = fren {
fren.update_fren(&global_data.db).unwrap(); debug!("Updating Lil Fren state...");
fren.update_fren(&data.db).unwrap();
} }
} }