Update to Poise v0.6

This commit is contained in:
Aadi Desai 2024-01-24 04:05:34 +00:00
parent 54a93953bf
commit a44f0ede7e
Signed by: supleed2
SSH key fingerprint: SHA256:CkbNRs0yVzXEiUp2zd0PSxsfRUMFF9bLlKXtE1xEbKM
15 changed files with 862 additions and 789 deletions

533
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,21 +11,21 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.75"
axum = "0.7.2"
anyhow = "1.0.79"
axum = "0.7.4"
indoc = "2.0.4"
poise = "0.5.7"
reqwest = { version = "0.11.22", features = ["json"] }
serde = { version = "1.0.193", features = ["derive"] }
shuttle-runtime = { version = "0.35.0", default-features = false }
shuttle-secrets = "0.35.0"
shuttle-shared-db = { version = "0.35.0", features = ["postgres"] }
poise = "0.6.1"
reqwest = { version = "0.11.23", features = ["json"] }
serde = { version = "1.0.195", features = ["derive"] }
shuttle-runtime = { version = "0.36.0", default-features = false }
shuttle-secrets = "0.36.0"
shuttle-shared-db = { version = "0.36.0", features = ["postgres"] }
sqlx = { version = "0.7.3", features = [
"macros",
"postgres",
"runtime-tokio-native-tls",
] }
tokio = "1.34.0"
tokio = "1.35.1"
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["time"] }
url = "2.5.0"

View file

@ -97,7 +97,7 @@ pub(crate) async fn refresh_non_members(ctx: ACtx<'_>) -> Result<(), Error> {
ctx.defer().await?;
let mut members = ctx.data().server.members_iter(ctx.http()).boxed();
let mut cnt = 0;
while let Some(Ok(mut m)) = members.next().await {
while let Some(Ok(m)) = members.next().await {
if m.roles.is_empty() {
m.add_role(ctx.http(), ctx.data().non_member).await?;
cnt += 1;
@ -122,7 +122,7 @@ pub(crate) async fn set_members_non_fresher(ctx: ACtx<'_>) -> Result<(), Error>
ctx.say(format!("{updated} updated to non-fresher, removing roles"))
.await?;
let mut members = ctx.data().server.members_iter(ctx.http()).boxed();
while let Some(Ok(mut m)) = members.next().await {
while let Some(Ok(m)) = members.next().await {
let _ = m.remove_role(ctx.http(), ctx.data().fresher).await;
}
ctx.say("Roles removed").await?;

View file

@ -1,6 +1,8 @@
use crate::{db, ACtx, Error, Gaijin};
use poise::serenity_prelude as serenity;
use poise::Modal;
use poise::{
serenity_prelude::{self as serenity, CreateAttachment, CreateMessage},
Modal,
};
use std::fmt::Write;
/// Get the number of entries in the gaijin table
@ -57,9 +59,11 @@ pub(crate) async fn get_all_gaijin(ctx: ACtx<'_>) -> Result<(), Error> {
ctx.say("Sending gaijin db data in followup message")
.await?;
ctx.channel_id()
.send_files(&ctx.http(), vec!["gaijin.rs"], |cm| {
cm.content("File: gaijin db")
})
.send_files(
&ctx.http(),
vec![CreateAttachment::path("gaijin.rs").await?],
CreateMessage::new().content("File: gaijin db"),
)
.await?;
}
Err(e) => {

View file

@ -1,5 +1,5 @@
use crate::{db, ACtx, Error, ManualMember};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{self as serenity, CreateAttachment, CreateMessage};
use poise::Modal;
/// Get the number of manual members in the manual table
@ -49,9 +49,11 @@ pub(crate) async fn get_all_manual(ctx: ACtx<'_>) -> Result<(), Error> {
ctx.say("Sending manual db data in followup message")
.await?;
ctx.channel_id()
.send_files(&ctx.http(), vec!["manual.rs"], |cm| {
cm.content("File: manual db")
})
.send_files(
&ctx.http(),
vec![CreateAttachment::path("manual.rs").await?],
CreateMessage::new().content("File: manual db"),
)
.await?;
}
Err(e) => {

View file

@ -1,5 +1,5 @@
use crate::{db, ACtx, Error, Member};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{self as serenity, CreateAttachment, CreateMessage};
use poise::Modal;
/// Get the number of members in the members table
@ -57,9 +57,11 @@ pub(crate) async fn get_all_members(ctx: ACtx<'_>) -> Result<(), Error> {
ctx.say("Sending members db data in followup message")
.await?;
ctx.channel_id()
.send_files(&ctx.http(), vec!["members.rs"], |cm| {
cm.content("File: members db")
})
.send_files(
&ctx.http(),
vec![CreateAttachment::path("members.rs").await?],
CreateMessage::new().content("File: members db"),
)
.await?;
}
Err(e) => {

View file

@ -1,5 +1,5 @@
use crate::{ACtx, Data, Error};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{self as serenity, CreateActionRow, CreateButton, CreateMessage};
use poise::Modal;
pub(crate) mod members;
@ -67,24 +67,21 @@ pub(crate) async fn setup(
.await?;
let emoji = emoji.unwrap_or_default().chars().next().unwrap_or('🚀');
channel
.send_message(ctx.http(), |m| {
m.content(message).components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Secondary)
.send_message(
ctx.http(),
CreateMessage::new()
.content(message)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("info")
.style(serenity::ButtonStyle::Secondary)
.emoji('📖')
.label("More info")
.custom_id("info")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
.label("More info"),
CreateButton::new("start")
.style(serenity::ButtonStyle::Primary)
.emoji(emoji)
.label(text.unwrap_or("Begin".to_string()))
.custom_id("start")
})
})
})
})
.label(text.unwrap_or("Begin".to_string())),
])]),
)
.await?;
} else {
ctx.say("Modal timed out, try again...").await?;

View file

@ -1,5 +1,5 @@
use crate::{db, ACtx, Error, PendingMember};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{self as serenity, CreateAttachment, CreateMessage};
use poise::Modal;
/// Get the number of pending members in the pending table
@ -49,9 +49,11 @@ pub(crate) async fn get_all_pending(ctx: ACtx<'_>) -> Result<(), Error> {
ctx.say("Sending pending db data in followup message")
.await?;
ctx.channel_id()
.send_files(&ctx.http(), vec!["pending.rs"], |cm| {
cm.content("File: pending db")
})
.send_files(
&ctx.http(),
vec![CreateAttachment::path("pending.rs").await?],
CreateMessage::new().content("File: pending db"),
)
.await?;
}
Err(e) => {

View file

@ -1,5 +1,5 @@
use crate::{db, ACtx, Error};
use poise::serenity_prelude as serenity;
use poise::{serenity_prelude as serenity, CreateReply};
use std::fmt::Write;
/// Unreachable, used to create whois command folder
@ -19,14 +19,19 @@ pub(crate) async fn whois_by_id(ctx: ACtx<'_>, id: serenity::Member) -> Result<(
tracing::info!("{} {}", ctx.author().name, id.user.name);
match db::get_member_by_id(&ctx.data().db, id.user.id.into()).await? {
Some(m) => {
ctx.send(|c| c.content(format!("{id}: {}", m.nickname)).ephemeral(true))
ctx.send(
CreateReply::default()
.content(format!("{id}: {}", m.nickname))
.ephemeral(true),
)
.await?
}
None => {
ctx.send(|c| {
c.content(format!("No member entry found for {id}"))
.ephemeral(true)
})
ctx.send(
CreateReply::default()
.content(format!("No member entry found for {id}"))
.ephemeral(true),
)
.await?
}
};
@ -39,29 +44,29 @@ pub(crate) async fn whois_by_id(ctx: ACtx<'_>, id: serenity::Member) -> Result<(
pub(crate) async fn whois_by_nickname(ctx: ACtx<'_>, nickname: String) -> Result<(), Error> {
tracing::info!("{} {nickname}", ctx.author().name);
if let Some(m) = db::get_member_by_nickname(&ctx.data().db, &nickname).await? {
ctx.send(|c| {
c.content(format!("{nickname}: <@{}>", m.discord_id))
.ephemeral(true)
})
ctx.send(
CreateReply::default()
.content(format!("{nickname}: <@{}>", m.discord_id))
.ephemeral(true),
)
.await?;
} else {
let members = db::get_member_by_nickname_fuzzy(&ctx.data().db, &nickname, 3).await?;
if members.is_empty() {
ctx.send(|c| {
c.content(format!("No member entry found for nickname {nickname}"))
.ephemeral(true)
})
ctx.send(
CreateReply::default()
.content(format!("No member entry found for nickname {nickname}"))
.ephemeral(true),
)
.await?;
} else {
ctx.send(|c| {
c.ephemeral(true).content(format!(
ctx.send(CreateReply::default().ephemeral(true).content(format!(
"Possible matches for {nickname}: {}",
members.iter().fold(String::new(), |mut s, g| {
write!(s, " <@{}>", g.discord_id).expect("String write! is infallible");
s
})
))
})
)))
.await?;
}
}
@ -74,29 +79,29 @@ pub(crate) async fn whois_by_nickname(ctx: ACtx<'_>, nickname: String) -> Result
pub(crate) async fn whois_by_realname(ctx: ACtx<'_>, realname: String) -> Result<(), Error> {
tracing::info!("{} {realname}", ctx.author().name);
if let Some(m) = db::get_member_by_realname(&ctx.data().db, &realname).await? {
ctx.send(|c| {
c.content(format!("{realname}: <@{}>", m.discord_id))
.ephemeral(true)
})
ctx.send(
CreateReply::default()
.content(format!("{realname}: <@{}>", m.discord_id))
.ephemeral(true),
)
.await?;
} else {
let members = db::get_member_by_realname_fuzzy(&ctx.data().db, &realname, 3).await?;
if members.is_empty() {
ctx.send(|c| {
c.content(format!("No member entry found for realname {realname}"))
.ephemeral(true)
})
ctx.send(
CreateReply::default()
.content(format!("No member entry found for realname {realname}"))
.ephemeral(true),
)
.await?;
} else {
ctx.send(|c| {
c.ephemeral(true).content(format!(
ctx.send(CreateReply::default().ephemeral(true).content(format!(
"Possible matches for {realname}: {}",
members.iter().fold(String::new(), |mut s, g| {
write!(s, " <@{}>", g.discord_id).expect("String write! is infallible");
s
})
))
})
)))
.await?;
}
};

View file

@ -1,6 +1,6 @@
#![warn(clippy::pedantic)]
use anyhow::Context as _;
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{self as serenity, FullEvent};
mod cmds;
mod db;
@ -59,14 +59,24 @@ struct Gaijin {
university: String,
}
macro_rules! secret {
($s: literal, $ss: ident) => {
$ss.get($s).context(format!("{} not found", $s))?
};
($s: literal, $ss: ident, $t: ty) => {
secret!($s, $ss)
.parse::<$t>()
.context(format!("{} not valid {}", $s, stringify!($t)))?
};
}
#[shuttle_runtime::main]
async fn poise(
async fn nanobot(
#[shuttle_secrets::Secrets] secret_store: shuttle_secrets::SecretStore,
#[shuttle_shared_db::Postgres] pool: sqlx::PgPool,
) -> Result<service::NanoBot, shuttle_runtime::Error> {
// Set Up Tracing Subscriber
init_tracing_subscriber();
tracing::info!("Tracing Subscriber Set Up");
// Run SQLx Migrations
sqlx::migrate!()
@ -74,58 +84,8 @@ async fn poise(
.await
.map_err(shuttle_runtime::CustomError::new)?;
// Load secrets
let au_ch_id = secret_store
.get("AU_CHANNEL_ID")
.expect("AU_CHANNEL_ID not found")
.parse()
.expect("AU_CHANNEL_ID not valid u64");
let ea_key = secret_store
.get("EA_API_KEY")
.expect("EA_API_KEY not found");
let ea_url = secret_store
.get("EA_API_URL")
.expect("EA_API_URL not found");
let token = secret_store
.get("DISCORD_TOKEN")
.expect("DISCORD_TOKEN not found");
let fresher = secret_store
.get("FRESHER_ID")
.expect("FRESHER_ID not found")
.parse()
.expect("FRESHER_ID not valid u64");
let gaijin = secret_store
.get("GAIJIN_ID")
.expect("GAIJIN_ID not found")
.parse()
.expect("GAIJIN_ID not valid u64");
let gn_ch_id = secret_store
.get("GN_CHANNEL_ID")
.expect("GN_CHANNEL_ID not found")
.parse()
.expect("GN_CHANNEL_ID not valid u64");
let member = secret_store
.get("MEMBER_ID")
.expect("MEMBER_ID not found")
.parse()
.expect("MEMBER_ID not valid u64");
let non_member = secret_store
.get("NON_MEMBER_ID")
.expect("NON_MEMBER_ID not found")
.parse()
.expect("NON_MEMBER_ID not valid u64");
let old_member = secret_store
.get("OLD_MEMBER_ID")
.expect("OLD_MEMBER_ID not found")
.parse()
.expect("OLD_MEMBER_ID not valid u64");
let server = secret_store
.get("SERVER_ID")
.expect("SERVER_ID not found")
.parse::<u64>()
.expect("SERVER_ID not valid u64")
.into();
tracing::info!("Secrets loaded");
// Load token
let token = secret!("DISCORD_TOKEN", secret_store);
// Build Axum Router
let router = axum::Router::new()
@ -142,33 +102,40 @@ async fn poise(
);
// Build Poise Instance
let discord = poise::Framework::builder()
let framework = poise::Framework::builder()
.options(poise::FrameworkOptions {
commands: all_commands(),
event_handler: { |c, e, f, d| Box::pin(event_handler(c, e, f, d)) },
..Default::default()
})
.token(token)
.intents(serenity::GatewayIntents::non_privileged())
.setup(move |ctx, _, _| {
Box::pin(async move {
ctx.set_activity(serenity::Activity::competing("autoverification"))
.await;
ctx.set_activity(Some(serenity::ActivityData::custom(
"Verifying members since 2023",
)));
Ok(Data {
au_ch_id,
au_ch_id: secret!("AU_CHANNEL_ID", secret_store, _),
db: pool,
ea_key,
ea_url,
fresher,
gaijin,
gn_ch_id,
member,
non_member,
old_member,
server,
ea_key: secret!("EA_API_KEY", secret_store),
ea_url: secret!("EA_API_URL", secret_store),
fresher: secret!("FRESHER_ID", secret_store, _),
gaijin: secret!("GAIJIN_ID", secret_store, _),
gn_ch_id: secret!("GN_CHANNEL_ID", secret_store, _),
member: secret!("MEMBER_ID", secret_store, _),
non_member: secret!("NON_MEMBER_ID", secret_store, _),
old_member: secret!("OLD_MEMBER_ID", secret_store, _),
server: secret!("SERVER_ID", secret_store, _),
})
})
});
})
.build();
// Build Discord struct
let discord = service::Discord {
framework,
token,
intents: serenity::GatewayIntents::non_privileged(),
};
// Return NanoBot
Ok(service::NanoBot { discord, router })
@ -176,16 +143,16 @@ async fn poise(
async fn event_handler(
ctx: &serenity::Context,
event: &poise::Event<'_>,
event: &FullEvent,
_framework: poise::FrameworkContext<'_, Data, Error>,
data: &Data,
) -> Result<(), Error> {
match event {
poise::Event::GuildMemberAddition { new_member } => {
FullEvent::GuildMemberAddition { new_member } => {
tracing::info!("Member joined: {}", new_member.user.name);
}
poise::Event::InteractionCreate {
interaction: serenity::Interaction::MessageComponent(m),
FullEvent::InteractionCreate {
interaction: serenity::Interaction::Component(m),
} => {
tracing::info!("Interaction: {} by {}", m.data.custom_id, m.user.name);
match m.data.custom_id.as_str() {
@ -214,8 +181,8 @@ async fn event_handler(
}
}
}
poise::Event::InteractionCreate {
interaction: serenity::Interaction::ModalSubmit(m),
FullEvent::InteractionCreate {
interaction: serenity::Interaction::Modal(m),
} => {
tracing::info!("Modal submit: {} by {}", m.data.custom_id, m.user.name);
match m.data.custom_id.as_str() {

View file

@ -1,10 +1,17 @@
use crate::{Data, Error};
use poise::serenity_prelude as serenity;
pub(crate) struct NanoBot {
pub discord: poise::FrameworkBuilder<Data, Error>,
pub discord: Discord,
pub router: axum::Router,
}
pub(crate) struct Discord {
pub framework: poise::Framework<Data, Error>,
pub token: String,
pub intents: serenity::GatewayIntents,
}
#[shuttle_runtime::async_trait]
impl shuttle_runtime::Service for NanoBot {
async fn bind(mut self, addr: std::net::SocketAddr) -> Result<(), shuttle_runtime::Error> {
@ -18,8 +25,13 @@ impl shuttle_runtime::Service for NanoBot {
)
.into_future();
let mut client = serenity::ClientBuilder::new(self.discord.token, self.discord.intents)
.framework(self.discord.framework)
.await
.map_err(shuttle_runtime::CustomError::new)?;
tokio::select! {
_ = self.discord.run_autosharded() => {},
_ = client.start_autosharded() => {},
_ = serve => {},
};

View file

@ -1,5 +1,8 @@
use crate::{Data, Error};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{
self as serenity, CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse,
CreateInteractionResponseMessage, CreateMessage,
};
use poise::Modal;
const LOGIN_INTRO: &str = indoc::indoc! {"
@ -14,34 +17,28 @@ const LOGIN_INTRO: &str = indoc::indoc! {"
#[tracing::instrument(skip_all)]
pub(crate) async fn login_1(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content(LOGIN_INTRO).components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.emoji('🔙')
.custom_id("restart")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Link)
let verify_url = format!("https://icas.8bitsqu.id/verify?id={}", m.user.id.get());
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(LOGIN_INTRO)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("restart")
.style(serenity::ButtonStyle::Danger)
.emoji('🔙'),
CreateButton::new_link(verify_url)
.emoji('🚀')
.label("Login Here")
.url(format!("https://icas.8bitsqu.id/verify?id={}", m.user.id.0))
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Secondary)
.label("Login Here"),
CreateButton::new("login_2")
.style(serenity::ButtonStyle::Secondary)
.emoji('👉')
.label("Then continue")
.custom_id("login_2")
})
})
})
})
})
.label("Then continue"),
])]),
),
)
.await?;
Ok(())
}
@ -55,52 +52,50 @@ const LOGIN_FORM: &str = indoc::indoc! {"
#[tracing::instrument(skip_all)]
pub(crate) async fn login_2(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
data: &Data,
) -> Result<(), Error> {
match crate::db::get_pending_by_id(&data.db, m.user.id.into()).await {
Err(e) => {
tracing::error!("{e}");
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Sorry, something went wrong. Please try again")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Sorry, something went wrong. Please try again")
.ephemeral(true),
),
)
.await?;
}
Ok(None) => {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Error, have you completed login verification via the link?")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Error, have you completed login verification via the link?")
.ephemeral(true),
),
)
.await?;
}
Ok(Some(_)) => {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content(LOGIN_FORM).components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.emoji('🔙')
.custom_id("login_1")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(LOGIN_FORM)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("login_1")
.style(serenity::ButtonStyle::Danger)
.emoji('🔙'),
CreateButton::new("login_3")
.style(serenity::ButtonStyle::Primary)
.emoji('📑')
.label("Form")
.custom_id("login_3")
})
})
})
})
})
.label("Form"),
])]),
),
)
.await?;
}
};
@ -110,34 +105,28 @@ pub(crate) async fn login_2(
#[tracing::instrument(skip_all)]
pub(crate) async fn login_3(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content("Are you a fresher?").components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.emoji('🔙')
.custom_id("login_2")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Success)
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content("Are you a fresher?")
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("login_2")
.style(serenity::ButtonStyle::Danger)
.emoji('🔙'),
CreateButton::new("login_4f")
.style(serenity::ButtonStyle::Success)
.emoji('✅')
.label("Fresher")
.custom_id("login_4f")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
.label("Fresher"),
CreateButton::new("login_4n")
.style(serenity::ButtonStyle::Primary)
.emoji('❌')
.label("Non-fresher")
.custom_id("login_4n")
})
})
})
})
})
.label("Non-fresher"),
])]),
),
)
.await?;
Ok(())
}
@ -145,30 +134,25 @@ pub(crate) async fn login_3(
#[tracing::instrument(skip_all)]
pub(crate) async fn login_4(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
fresher: bool,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content("And a preferred name for Nano whois commands")
.components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.emoji('🔙')
.custom_id("login_3")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content("And a preferred name for Nano whois commands")
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("login_3")
.style(serenity::ButtonStyle::Danger)
.emoji('🔙'),
CreateButton::new(if fresher { "login_5f" } else { "login_5n" })
.style(serenity::ButtonStyle::Primary)
.emoji('💬')
.label("Name")
.custom_id(if fresher { "login_5f" } else { "login_5n" })
})
})
})
})
})
.label("Name"),
])]),
),
)
.await?;
Ok(())
}
@ -184,20 +168,20 @@ struct Nickname {
#[tracing::instrument(skip_all)]
pub(crate) async fn login_5(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
fresher: bool,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
*i = Nickname::create(
m.create_response(
&ctx.http,
Nickname::create(
None,
if fresher {
"login_6f".to_string()
} else {
"login_6n".to_string()
},
);
i
})
),
)
.await?;
Ok(())
}
@ -205,7 +189,7 @@ pub(crate) async fn login_5(
#[tracing::instrument(skip_all)]
pub(crate) async fn login_6(
ctx: &serenity::Context,
m: &serenity::ModalSubmitInteraction,
m: &serenity::ModalInteraction,
data: &Data,
fresher: bool,
) -> Result<(), Error> {
@ -234,34 +218,38 @@ pub(crate) async fn login_6(
if fresher {
crate::verify::apply_role(ctx, &mut mm, data.fresher).await?;
}
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content(if fresher {
let msg = if fresher {
"Congratulations, you have completed verification and now \
have access to the ICAS Discord and freshers thread"
} else {
"Congratulations, you have completed verification and now \
have access to the ICAS Discord"
})
.components(|c| c)
})
})
};
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(msg)
.components(vec![]),
),
)
.await?;
data.au_ch_id
.send_message(&ctx.http, |cm| {
cm.add_embed(|e| {
e.thumbnail(
.send_message(
&ctx.http,
CreateMessage::new().embed(
CreateEmbed::new()
.thumbnail(
m.user.avatar_url().unwrap_or(super::AVATAR.to_string()),
)
.title("Member verified via login")
.description(&m.user)
.field("Fresher", fresher, true)
.description(m.user.to_string())
.field("Fresher", fresher.to_string(), true)
.field("Nickname", nickname, true)
.field("Name", p.realname, true)
.timestamp(serenity::Timestamp::now())
})
})
.timestamp(serenity::Timestamp::now()),
),
)
.await?;
let _ = mm.remove_role(&ctx.http, data.non_member).await;
if mm.roles.contains(&data.old_member) {
@ -273,13 +261,14 @@ pub(crate) async fn login_6(
}
Err(e) => {
tracing::error!("Error: {e}");
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Sorry, something went wrong. Please try again")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Sorry, something went wrong. Please try again")
.ephemeral(true),
),
)
.await?;
}
}

View file

@ -1,5 +1,8 @@
use crate::{Data, Error};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{
self as serenity, CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse,
CreateInteractionResponseMessage, CreateMessage,
};
use poise::Modal;
const MANUAL_INTRO: &str = indoc::indoc! {"
@ -19,34 +22,28 @@ const MANUAL_INTRO: &str = indoc::indoc! {"
#[tracing::instrument(skip_all)]
pub(crate) async fn manual_1(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content(MANUAL_INTRO).components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.emoji('🔙')
.custom_id("restart")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Success)
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(MANUAL_INTRO)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("restart")
.style(serenity::ButtonStyle::Danger)
.emoji('🔙'),
CreateButton::new("manual_2f")
.style(serenity::ButtonStyle::Success)
.emoji('✅')
.label("Fresher")
.custom_id("manual_2f")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
.label("Fresher"),
CreateButton::new("manual_2n")
.style(serenity::ButtonStyle::Primary)
.emoji('❌')
.label("Non-fresher")
.custom_id("manual_2n")
})
})
})
})
})
.label("Non-fresher"),
])]),
),
)
.await?;
Ok(())
}
@ -72,24 +69,24 @@ struct Manual {
#[tracing::instrument(skip_all)]
pub(crate) async fn manual_2(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
data: &Data,
fresher: bool,
) -> Result<(), Error> {
// Delete from manual if exists
let _ = crate::db::delete_manual_by_id(&data.db, m.user.id.into()).await;
m.create_interaction_response(&ctx.http, |i| {
*i = Manual::create(
m.create_response(
&ctx.http,
Manual::create(
None,
if fresher {
"manual_3f".to_string()
} else {
"manual_3n".to_string()
},
);
i
})
),
)
.await?;
Ok(())
}
@ -97,7 +94,7 @@ pub(crate) async fn manual_2(
#[tracing::instrument(skip_all)]
pub(crate) async fn manual_3(
ctx: &serenity::Context,
m: &serenity::ModalSubmitInteraction,
m: &serenity::ModalInteraction,
data: &Data,
fresher: bool,
) -> Result<(), Error> {
@ -109,13 +106,14 @@ pub(crate) async fn manual_3(
nickname,
}) => {
if ::url::Url::parse(&url).is_err() {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("The url provided is invalid, please try again")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("The url provided is invalid, please try again")
.ephemeral(true),
),
)
.await?;
return Ok(());
}
@ -125,36 +123,33 @@ pub(crate) async fn manual_3(
let prompt_sent = data
.au_ch_id
.send_message(&ctx.http, |cm| {
cm.add_embed(|e| {
e.title("New verification request from")
.send_message(
&ctx.http,
CreateMessage::new()
.embed(
CreateEmbed::new()
.title("New verification request from")
.thumbnail(m.user.avatar_url().unwrap_or(super::AVATAR.to_string()))
.description(&m.user)
.description(m.user.to_string())
.field("Real Name (To be checked)", &realname, true)
.field("Imperial Shortcode (To be checked", &shortcode, true)
.field("Fresher (To be checked)", fresher, true)
.field("Fresher (To be checked)", fresher.to_string(), true)
.field("Nickname (Nano whois commands)", &nickname, true)
.field("Verification URL (Also displayed below)", &url, true)
.image(&url)
.timestamp(serenity::Timestamp::now())
})
.components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Success)
.timestamp(serenity::Timestamp::now()),
)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new(format!("verify-y-{}", m.user.id))
.style(serenity::ButtonStyle::Success)
.emoji('✅')
.label("Accept")
.custom_id(format!("verify-y-{}", m.user.id))
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.label("Accept"),
CreateButton::new(format!("verify-n-{}", m.user.id))
.style(serenity::ButtonStyle::Danger)
.emoji('❎')
.label("Deny")
.custom_id(format!("verify-n-{}", m.user.id))
})
})
})
})
.label("Deny"),
])]),
)
.await
.is_ok();
@ -181,22 +176,27 @@ pub(crate) async fn manual_3(
"Sending your verification request failed, please try again."
};
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| d.content(msg).components(|c| c))
})
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(msg)
.components(vec![]),
),
)
.await?;
return Ok(());
}
Err(e) => tracing::error!("{e}"),
};
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Sorry, something went wrong. Please try again")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Sorry, something went wrong. Please try again")
.ephemeral(true),
),
)
.await?;
Ok(())
}
@ -204,7 +204,7 @@ pub(crate) async fn manual_3(
#[tracing::instrument(skip_all)]
pub(crate) async fn manual_4(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
data: &Data,
id: &str,
) -> Result<(), Error> {
@ -214,7 +214,7 @@ pub(crate) async fn manual_4(
.skip(9)
.collect::<String>()
.parse::<u64>()
.map(serenity::UserId)
.map(serenity::UserId::new)
.unwrap_or_default()
.to_user(ctx)
.await
@ -239,20 +239,25 @@ pub(crate) async fn manual_4(
if fresher {
crate::verify::apply_role(ctx, &mut member, data.fresher).await?;
}
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.components(|c| c).embed(|e| {
e.thumbnail(user.avatar_url().unwrap_or(super::AVATAR.to_string()))
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.components(vec![])
.embed(
CreateEmbed::new()
.thumbnail(
user.avatar_url().unwrap_or(super::AVATAR.to_string()),
)
.title("Member verified via manual")
.description(&user)
.field("Fresher", fresher, true)
.description(user.to_string())
.field("Fresher", fresher.to_string(), true)
.field("Nickname", mm.nickname, true)
.field("Name", mm.realname, true)
.timestamp(serenity::Timestamp::now())
})
})
})
.timestamp(serenity::Timestamp::now()),
),
),
)
.await?;
let _ = member.remove_role(&ctx.http, data.non_member).await;
if member.roles.contains(&data.old_member) {
@ -263,29 +268,33 @@ pub(crate) async fn manual_4(
}
Err(e) => {
tracing::error!("{e}");
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content(format!("Failed to add user {user} to member database"))
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(format!("Failed to add user {user} to member database")),
),
)
.await?;
}
}
} else {
crate::db::delete_manual_by_id(&data.db, user.id.into()).await?;
tracing::info!("{} ({}) denied via manual", user.name, user.id);
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.components(|c| c).embed(|e| {
e.title("Member denied via manual")
.description(&user)
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.components(vec![])
.embed(
CreateEmbed::new()
.title("Member denied via manual")
.description(user.to_string())
.thumbnail(user.avatar_url().unwrap_or(super::AVATAR.to_string()))
.timestamp(serenity::Timestamp::now())
})
})
})
.timestamp(serenity::Timestamp::now()),
),
),
)
.await?;
}

View file

@ -1,5 +1,8 @@
use crate::{Data, Error};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{
self as serenity, CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse,
CreateInteractionResponseMessage, CreateMessage,
};
use poise::Modal;
const MEMBERSHIP_INTRO: &str = indoc::indoc! {"
@ -17,34 +20,28 @@ const MEMBERSHIP_INTRO: &str = indoc::indoc! {"
#[tracing::instrument(skip_all)]
pub(crate) async fn membership_1(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content(MEMBERSHIP_INTRO).components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Danger)
.emoji('🔙')
.custom_id("restart")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Success)
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(MEMBERSHIP_INTRO)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("restart")
.style(serenity::ButtonStyle::Danger)
.emoji('🔙'),
CreateButton::new("membership_2f")
.style(serenity::ButtonStyle::Success)
.emoji('✅')
.label("Fresher")
.custom_id("membership_2f")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
.label("Fresher"),
CreateButton::new("membership_2n")
.style(serenity::ButtonStyle::Primary)
.emoji('❌')
.label("Non-fresher")
.custom_id("membership_2n")
})
})
})
})
})
.label("Non-fresher"),
])]),
),
)
.await?;
Ok(())
}
@ -66,7 +63,7 @@ struct Membership {
#[tracing::instrument(skip_all)]
pub(crate) async fn membership_2(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
data: &Data,
fresher: bool,
) -> Result<(), Error> {
@ -76,17 +73,17 @@ pub(crate) async fn membership_2(
// Delete from manual if exists
let _ = crate::db::delete_manual_by_id(&data.db, m.user.id.into()).await;
m.create_interaction_response(&ctx.http, |i| {
*i = Membership::create(
m.create_response(
&ctx.http,
Membership::create(
None,
if fresher {
"membership_3f".to_string()
} else {
"membership_3n".to_string()
},
);
i
})
),
)
.await?;
Ok(())
}
@ -94,7 +91,7 @@ pub(crate) async fn membership_2(
#[tracing::instrument(skip_all)]
pub(crate) async fn membership_3(
ctx: &serenity::Context,
m: &serenity::ModalSubmitInteraction,
m: &serenity::ModalInteraction,
data: &Data,
fresher: bool,
) -> Result<(), Error> {
@ -108,13 +105,14 @@ pub(crate) async fn membership_3(
Ok(v) => v,
Err(e) => {
tracing::error!("{e}");
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Sorry, getting membership data failed. Please try again")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Sorry, getting membership data failed. Please try again")
.ephemeral(true),
),
)
.await?;
return Ok(());
}
@ -123,12 +121,16 @@ pub(crate) async fn membership_3(
((member.login.is_empty() && member.cid == shortcode) || member.login == shortcode)
&& member.order_no.to_string() == order
}) else {
m.create_interaction_response(&ctx.http, |i| {
let msg = "Sorry, your order was not found, please check the \
order number and that it is for your current year's membership";
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| d.content(msg).ephemeral(true))
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(msg)
.ephemeral(true),
),
)
.await?;
return Ok(());
};
@ -157,32 +159,35 @@ pub(crate) async fn membership_3(
if fresher {
crate::verify::apply_role(ctx, &mut mm, data.fresher).await?;
}
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.content(if fresher {
m.create_response(
&ctx.http,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.content(if fresher {
"Congratulations, you have completed verification and now \
have access to the ICAS Discord and freshers thread"
} else {
"Congratulations, you have completed verification and now \
have access to the ICAS Discord"
})
.components(|c| c)
})
})
.components(vec![]),
),
)
.await?;
data.au_ch_id
.send_message(&ctx.http, |cm| {
cm.add_embed(|e| {
e.thumbnail(m.user.avatar_url().unwrap_or(super::AVATAR.to_string()))
.send_message(
&ctx.http,
CreateMessage::new().embed(
CreateEmbed::new()
.thumbnail(m.user.avatar_url().unwrap_or(super::AVATAR.to_string()))
.title("Member verified via membership")
.description(&m.user)
.field("Fresher", fresher, true)
.description(m.user.to_string())
.field("Fresher", fresher.to_string(), true)
.field("Nickname", nickname, true)
.field("Name", realname, true)
.timestamp(serenity::Timestamp::now())
})
})
.timestamp(serenity::Timestamp::now()),
),
)
.await?;
let _ = mm.remove_role(&ctx.http, data.non_member).await;
if mm.roles.contains(&data.old_member) {
@ -196,13 +201,14 @@ pub(crate) async fn membership_3(
}
Err(e) => tracing::error!("{e}"),
};
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Sorry, something went wrong. Please try again")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Sorry, something went wrong. Please try again")
.ephemeral(true),
),
)
.await?;
Ok(())
}

View file

@ -1,5 +1,8 @@
use crate::{Data, Error};
use poise::serenity_prelude as serenity;
use poise::serenity_prelude::{
self as serenity, CacheHttp, CreateActionRow, CreateButton, CreateInteractionResponse,
CreateInteractionResponseMessage, CreateMessage,
};
pub(crate) mod login;
pub(crate) use login::*;
@ -23,12 +26,16 @@ const INFO_MSG: &str = indoc::indoc! {"
#[tracing::instrument(skip_all)]
pub(crate) async fn info(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| d.content(INFO_MSG).ephemeral(true))
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(INFO_MSG)
.ephemeral(true),
),
)
.await?;
Ok(())
}
@ -36,15 +43,19 @@ pub(crate) async fn info(
#[tracing::instrument(skip_all)]
pub(crate) async fn unknown(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
) -> Result<(), Error> {
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Sorry, something went wrong. Please try again or message <@99217900254035968> for help")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content(
"Sorry, something went wrong. Please try again \
or message <@99217900254035968> for help",
)
.ephemeral(true),
),
)
.await?;
Ok(())
}
@ -59,7 +70,7 @@ const START_MSG: &str = indoc::indoc! {"
#[tracing::instrument(skip_all)]
pub(crate) async fn start(
ctx: &serenity::Context,
m: &serenity::MessageComponentInteraction,
m: &serenity::ComponentInteraction,
data: &Data,
init: bool,
) -> Result<(), Error> {
@ -70,46 +81,41 @@ pub(crate) async fn start(
if member.fresher {
apply_role(ctx, &mut mm, data.fresher).await?;
}
m.create_interaction_response(&ctx.http, |i| {
i.kind(serenity::InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content("Welcome, you're already verified, re-applied your roles!")
.ephemeral(true)
})
})
m.create_response(
&ctx.http,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Welcome, you're already verified, re-applied your roles!")
.ephemeral(true),
),
)
.await?;
} else {
m.create_interaction_response(&ctx.http, |i| {
i.kind(if init {
serenity::InteractionResponseType::ChannelMessageWithSource
} else {
serenity::InteractionResponseType::UpdateMessage
})
.interaction_response_data(|d| {
d.content(START_MSG).ephemeral(true).components(|c| {
c.create_action_row(|a| {
a.create_button(|b| {
b.style(serenity::ButtonStyle::Primary)
let irm = CreateInteractionResponseMessage::new()
.content(START_MSG)
.ephemeral(true)
.components(vec![CreateActionRow::Buttons(vec![
CreateButton::new("login_1")
.style(serenity::ButtonStyle::Primary)
.emoji('🚀')
.label("Login")
.custom_id("login_1")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Secondary)
.label("Login"),
CreateButton::new("membership_1")
.style(serenity::ButtonStyle::Secondary)
.emoji(serenity::ReactionType::Unicode("✈️".to_string()))
.label("Membership")
.custom_id("membership_1")
})
.create_button(|b| {
b.style(serenity::ButtonStyle::Secondary)
.label("Membership"),
CreateButton::new("manual_1")
.style(serenity::ButtonStyle::Secondary)
.emoji('🚗')
.label("Manual")
.custom_id("manual_1")
})
})
})
})
})
.label("Manual"),
])]);
m.create_response(
&ctx.http,
if init {
CreateInteractionResponse::Message(irm)
} else {
CreateInteractionResponse::UpdateMessage(irm)
},
)
.await?;
};
Ok(())
@ -135,14 +141,15 @@ pub(crate) async fn remove_role(
#[tracing::instrument(skip_all)]
pub(crate) async fn welcome_user(
http: impl AsRef<serenity::http::Http>,
http: impl CacheHttp,
channel: &serenity::ChannelId,
user: &serenity::User,
fresher: bool,
) -> Result<(), Error> {
channel
.send_message(http, |m| {
m.content(format!(
.send_message(
http,
CreateMessage::new().content(format!(
"Welcome to ICAS {user}, if you have any questions, \
feel free to ping a committee member{}!",
if fresher {
@ -150,8 +157,8 @@ pub(crate) async fn welcome_user(
} else {
""
}
))
})
)),
)
.await?;
Ok(())
}