320 lines
9.5 KiB
Rust
320 lines
9.5 KiB
Rust
use chrono::DateTime;
|
|
use poise::serenity_prelude::{self as serenity, AttachmentType, RichInvite};
|
|
use rusted_fbt_lib::{
|
|
checks::guild_auth_check,
|
|
types::{Context, Error},
|
|
utils::snowflake_to_unix,
|
|
};
|
|
use serde::Deserialize;
|
|
use tracing::instrument;
|
|
|
|
use crate::commands::database::check_username_against_db;
|
|
|
|
#[instrument(skip(ctx))]
|
|
#[poise::command(slash_command, track_edits, category = "Tools")]
|
|
/// Display your or another user's account creation date
|
|
pub async fn account_age(
|
|
ctx: Context<'_>,
|
|
#[description = "Selected user"] user: Option<serenity::User>,
|
|
) -> Result<(), Error> {
|
|
let user = user.as_ref().unwrap_or_else(|| ctx.author());
|
|
|
|
let uid = *user.id.as_u64();
|
|
|
|
let unix_timecode = snowflake_to_unix(u128::from(uid));
|
|
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
// this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN
|
|
let date_time_stamp =
|
|
DateTime::from_timestamp(unix_timecode as i64, 0).unwrap_or(DateTime::UNIX_EPOCH);
|
|
|
|
let age = chrono::Utc::now()
|
|
.naive_utc()
|
|
.signed_duration_since(date_time_stamp.naive_local())
|
|
.num_days();
|
|
|
|
ctx.say(format!(
|
|
"{}'s account was created at {}.\nSo They are {} days old.",
|
|
user.name,
|
|
user.created_at(),
|
|
age
|
|
))
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Gets the creation date or a Snowflake ID
|
|
#[instrument(skip(ctx))]
|
|
#[poise::command(prefix_command, slash_command, category = "Tools")]
|
|
pub async fn creation_date(
|
|
ctx: Context<'_>,
|
|
#[description = "ID of User/Message/Channel/ect"] snowflake_id: u128,
|
|
) -> Result<(), Error> {
|
|
let unix_timecode = snowflake_to_unix(snowflake_id);
|
|
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
// this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN
|
|
let date_time_stamp =
|
|
DateTime::from_timestamp(unix_timecode as i64, 0).unwrap_or(DateTime::UNIX_EPOCH);
|
|
|
|
ctx.say(format!("Created/Joined on {date_time_stamp}"))
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// qmit
|
|
#[instrument(skip(ctx))]
|
|
#[poise::command(owners_only, slash_command, hide_in_help)]
|
|
pub async fn bot_owner_tool_1(ctx: Context<'_>) -> Result<(), Error> {
|
|
ctx.defer_ephemeral().await?;
|
|
|
|
let guild_list = ctx.serenity_context().cache.guilds();
|
|
|
|
let mut invites: Vec<RichInvite> = Vec::new();
|
|
|
|
for guild in guild_list {
|
|
let guild_invites: Option<Vec<RichInvite>> = (guild.invites(ctx).await).ok();
|
|
|
|
if guild_invites.clone().is_some() {
|
|
invites.append(&mut guild_invites.unwrap());
|
|
}
|
|
}
|
|
|
|
// let shit_list = format!("All invites the bot can see:\n\n{:?}", invites);
|
|
|
|
let mut new_list: String = "Every invite the bot can see, grouped by guild:\n\n[\n".to_string();
|
|
|
|
for invite in invites {
|
|
new_list.push_str(format!("{},\n", serde_json::to_string(&invite)?).as_str());
|
|
}
|
|
|
|
new_list.push(']');
|
|
|
|
ctx.send(|b| {
|
|
b.content("All bot invites:".to_string())
|
|
.attachment(AttachmentType::Bytes {
|
|
data: std::borrow::Cow::Borrowed(new_list.as_bytes()),
|
|
filename: format!("{}_invites.txt", ctx.id()),
|
|
})
|
|
})
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Get's all avaliable info from a Discord Invite
|
|
#[instrument(skip(ctx))]
|
|
#[poise::command(
|
|
prefix_command,
|
|
slash_command,
|
|
category = "Tools",
|
|
member_cooldown = 5,
|
|
check = "guild_auth_check",
|
|
guild_only
|
|
)]
|
|
pub async fn invite_info(
|
|
ctx: Context<'_>,
|
|
#[description = "Invite URL"] invite_url: String,
|
|
) -> Result<(), Error> {
|
|
use linkify::LinkFinder;
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct InviteObject {
|
|
#[serde(rename(deserialize = "type"))]
|
|
_type: u64,
|
|
code: String,
|
|
inviter: InviterObject,
|
|
expires_at: Option<String>,
|
|
guild: PartialGuild,
|
|
guild_id: String,
|
|
channel: PartialChannel,
|
|
approximate_member_count: u64,
|
|
approximate_presence_count: u64,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct InviterObject {
|
|
id: String,
|
|
username: String,
|
|
#[allow(dead_code)]
|
|
avatar: Option<String>,
|
|
discriminator: Option<String>,
|
|
#[allow(dead_code)]
|
|
public_flags: u64,
|
|
#[allow(dead_code)]
|
|
flags: u64,
|
|
#[allow(dead_code)]
|
|
banner: Option<String>,
|
|
#[allow(dead_code)]
|
|
accent_color: Option<u64>,
|
|
global_name: Option<String>,
|
|
#[allow(dead_code)]
|
|
avatar_decoration_data: Option<String>,
|
|
#[allow(dead_code)]
|
|
banner_color: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct PartialGuild {
|
|
#[allow(dead_code)]
|
|
id: String,
|
|
name: String,
|
|
#[allow(dead_code)]
|
|
splash: Option<String>,
|
|
#[allow(dead_code)]
|
|
banner: Option<String>,
|
|
description: Option<String>,
|
|
#[allow(dead_code)]
|
|
icon: Option<String>,
|
|
#[allow(dead_code)]
|
|
features: Vec<String>,
|
|
#[allow(dead_code)]
|
|
verification_level: u64,
|
|
vanity_url_code: Option<String>,
|
|
#[allow(dead_code)]
|
|
nsfw_level: u64,
|
|
#[allow(dead_code)]
|
|
nsfw: bool,
|
|
premium_subscription_count: u64,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize, Clone)]
|
|
struct PartialChannel {
|
|
id: String,
|
|
#[serde(rename(deserialize = "type"))]
|
|
_type: u64,
|
|
name: String,
|
|
}
|
|
|
|
let finder = LinkFinder::new();
|
|
let links: Vec<_> = finder.links(&invite_url).collect();
|
|
|
|
if links.is_empty() {
|
|
ctx.say("No valid links found").await?;
|
|
return Ok(());
|
|
}
|
|
|
|
let link_str = links[0].as_str().to_owned();
|
|
|
|
let (_link, invite_code) = link_str.split_at(19);
|
|
|
|
let response = reqwest::get(format!(
|
|
"https://discord.com/api/v10/invites/{invite_code}?with_counts=true"
|
|
))
|
|
.await?;
|
|
let response_formatted: Option<InviteObject> = response.json().await?;
|
|
|
|
if response_formatted.is_none() {
|
|
ctx.say("Invite not found").await?;
|
|
return Ok(());
|
|
}
|
|
|
|
let invite = response_formatted.unwrap();
|
|
|
|
let invite_info_fields = vec![
|
|
("Code:", invite.code, false),
|
|
("Expires:", invite.expires_at.unwrap_or_default(), false),
|
|
("Destination channel name:", invite.channel.name, false),
|
|
("Destination channel ID:", invite.channel.id, false),
|
|
];
|
|
|
|
let guild_info_fields = vec![
|
|
("Server name:", invite.guild.name, false),
|
|
("Server ID:", invite.guild_id, false),
|
|
(
|
|
"Server Description:",
|
|
invite.guild.description.unwrap_or_default(),
|
|
true,
|
|
),
|
|
(
|
|
"Vanity URL code:",
|
|
invite.guild.vanity_url_code.unwrap_or_default(),
|
|
false,
|
|
),
|
|
(
|
|
"Server boosts count:",
|
|
format!("{}", invite.guild.premium_subscription_count),
|
|
false,
|
|
),
|
|
(
|
|
"Approx member count:",
|
|
format!("{}", invite.approximate_member_count),
|
|
false,
|
|
),
|
|
(
|
|
"Approx online user count:",
|
|
format!("{}", invite.approximate_presence_count),
|
|
false,
|
|
),
|
|
];
|
|
|
|
let inviter_info_fields = vec![
|
|
("Username:", invite.inviter.username, false),
|
|
(
|
|
"Global username:",
|
|
invite.inviter.global_name.unwrap_or_default(),
|
|
false,
|
|
),
|
|
("User ID:", invite.inviter.id.clone(), false),
|
|
(
|
|
"Discriminator(Eg: #0001):",
|
|
invite.inviter.discriminator.unwrap_or_default(),
|
|
false,
|
|
),
|
|
];
|
|
|
|
ctx.send(|f| {
|
|
f.embed(|e| e.title("Invite Info").fields(invite_info_fields))
|
|
.embed(|e| e.title("Guild Info").fields(guild_info_fields))
|
|
.embed(|e| e.title("Inviter Info").fields(inviter_info_fields))
|
|
})
|
|
.await?;
|
|
|
|
let unix_timecode = snowflake_to_unix(u128::from(ctx.author().id.0));
|
|
|
|
#[allow(clippy::cast_possible_truncation)]
|
|
// this shouldn't be able to break but just in case I'm making the `unwrap_or` output NaiveDateTime::MIN
|
|
let date_time_stamp =
|
|
DateTime::from_timestamp(unix_timecode as i64, 0).unwrap_or(DateTime::UNIX_EPOCH);
|
|
|
|
let age = chrono::Utc::now()
|
|
.naive_utc()
|
|
.signed_duration_since(date_time_stamp.naive_local())
|
|
.num_days();
|
|
|
|
let is_user_in_db: Option<String> =
|
|
check_username_against_db(invite.inviter.id.parse::<u64>().unwrap())
|
|
.await
|
|
.unwrap();
|
|
|
|
|
|
// TODO: set your own channel ID!
|
|
|
|
// log user name, id, guild name, id and url to channel
|
|
serenity::ChannelId(00000000000000000)
|
|
.send_message(ctx, |f| {
|
|
f.embed(|e| {
|
|
e.title("User requested invite info")
|
|
.field("Username", ctx.author().name.clone(), true)
|
|
.field("User ID", ctx.author().id.0.to_string(), true)
|
|
.field("User Account age (days)", age, true)
|
|
.field("Source Server Name", ctx.guild().unwrap().name, true)
|
|
.field(
|
|
"Source Server ID",
|
|
ctx.guild().unwrap().id.0.to_string(),
|
|
true,
|
|
)
|
|
.field("Url provided", link_str, true)
|
|
.field(
|
|
"Is User in DB",
|
|
format!("{}", is_user_in_db.is_some()),
|
|
false,
|
|
)
|
|
})
|
|
})
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|