#![allow(clippy::unreadable_literal)] #[macro_use] extern crate lazy_static; use rand::Rng; use serenity::{ client::Client, framework::standard::{ macros::{check, command, group}, Args, CheckResult, CommandOptions, CommandResult, DispatchError, Reason, StandardFramework, }, model::{channel::Message, gateway::Ready, id::UserId, user::OnlineStatus}, prelude::*, }; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::{env, process}; struct Handler; impl EventHandler for Handler { fn ready(&self, ctx: Context, ready: Ready) { if let Some(shard) = ready.shard { println!( "INFO: {} is connected on shard {}/{}!\nuwu", ready.user.name, shard[0], shard[1] ); use serenity::model::gateway::Activity; let activity = Activity::listening("catgirls nyaaing"); let status = OnlineStatus::Online; ctx.set_presence(Some(activity), status); } } } #[group] #[commands( init, ping, halt, list_srv, host, ship, bottom_rng, headpat, uwu, gayculator, waffle, sausage, help, embed, what, owo, info )] struct General; lazy_static! { static ref OWNERS: std::vec::Vec = vec![UserId(254310746450690048), UserId(687740609703706630)]; } // Calculates hash of a type that implements Hash fn calculate_hash(t: &T) -> u64 { let mut s = DefaultHasher::new(); t.hash(&mut s); s.finish() } fn main() { let mut client = Client::new(&env::var("DISCORD_TOKEN").expect("Invalid token"), Handler) .expect("Error creating client"); client.with_framework( StandardFramework::new() .configure(|c| { c.with_whitespace(true) .owners(OWNERS.clone().into_iter().collect()) .prefixes(vec!["owo ", "OwO "]) .no_dm_prefix(true) .case_insensitivity(true) .by_space(false) }) .on_dispatch_error(|ctx, msg, error| { if let DispatchError::CheckFailed("Owner", Reason::Unknown) = error { let _ = msg.channel_id.say(&ctx.http, "nyo"); } else if let DispatchError::Ratelimited(seconds) = error { let _ = msg .channel_id .say(&ctx.http, &format!("Try again in {} seconds.", seconds)); } }) .group(&GENERAL_GROUP), ); if let Err(e) = client.start() { println!("An error occurred while running the client: {:?}", e); } } #[check] #[name = "Owner"] fn owner_check(_: &mut Context, msg: &Message, _: &mut Args, _: &CommandOptions) -> CheckResult { if OWNERS.clone().contains(&msg.author.id) { CheckResult::Success } else { CheckResult::Failure(Reason::Unknown) } } #[check] #[name = "Server"] fn server_check(_: &mut Context, msg: &Message, _: &mut Args, _: &CommandOptions) -> CheckResult { (msg.guild_id == Some(serenity::model::id::GuildId(687011389294116875))).into() } #[command] fn init(ctx: &mut Context, message: &Message) -> CommandResult { let responses = [ "Discordinator9000 is gonna hug nya'll!", "Nyaa~!", "Hewwo uwu", ]; let num = rand::thread_rng().gen_range(0, responses.len()); let _ = message.channel_id.say(&ctx.http, responses[num]); Ok(()) } #[command] fn ping(ctx: &mut Context, message: &Message) -> CommandResult { let _ = message.reply(&ctx, "Pong!"); Ok(()) } #[command] #[checks(Owner)] fn halt(ctx: &mut Context) -> CommandResult { ctx.set_presence(None, OnlineStatus::Offline); use std::{thread, time}; // Sleep for 1s thread::sleep(time::Duration::new(1, 0)); process::exit(0); } #[command] #[checks(Owner)] fn list_srv(ctx: &mut Context, message: &Message) -> CommandResult { let mut list = String::new(); let cache = ctx.cache.read(); for (index, guild_lock) in cache.guilds.values().enumerate() { let guild = guild_lock.read(); list.push_str(&format!("{}: {}\n", index, guild.name)); } let _ = message .channel_id .say(&ctx.http, list.replace("@", "@\u{200B}")); Ok(()) } #[command] #[checks(Owner)] fn host(ctx: &mut Context, message: &Message) -> CommandResult { let _ = message.channel_id.say( &ctx.http, format!( "Debug\nOS: {os}; {release}\nHost: {host}\nCPU: {cpu}MHz", os = sys_info::os_type().unwrap(), host = sys_info::hostname().unwrap(), release = sys_info::linux_os_release() .unwrap() .pretty_name .unwrap_or_else(|| "Unknown".to_string()), cpu = sys_info::cpu_speed().unwrap() ), ); Ok(()) } #[command] fn embed(ctx: &mut Context, message: &Message, args: Args) -> CommandResult { use serde::Deserialize; use serenity::utils::Colour; #[derive(Deserialize)] struct EmbedProperties { author: Option<(String, String)>, colour: String, description: Option, fields: Option>, footer: Option<(String, String)>, image: Option, timestamp: Option, title: Option, url: Option, } let input_embed: EmbedProperties = match toml::from_str(args.rest().trim()) { Ok(v) => v, Err(e) => { eprintln!("Deserialization error: {:?}", e); EmbedProperties { author: None, colour: 000000.to_string(), description: None, fields: None, footer: None, image: None, timestamp: None, title: None, url: None, } } }; let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { // Set embed author unless empty if input_embed.author != None { let auth = input_embed.author.unwrap(); e.author(|a| { //assuming first array element is name and second is icon url a.name(auth.0); a.icon_url(auth.1); a }); } e.color(Colour::new( u32::from_str_radix(&input_embed.colour, 16) .ok() .unwrap_or(0x000000), )); // Set embed description unless empty if input_embed.description != None { e.description(input_embed.description.unwrap()); } // Set embed fields unless empty if input_embed.fields != None { e.fields(input_embed.fields.unwrap()); } // Set embed footer unless empty if input_embed.footer != None { let foot = input_embed.footer.unwrap(); e.footer(|f| { //assuming first array element is name and second is icon url f.text(foot.0); f.icon_url(foot.1); f }); } if input_embed.image != None { e.image(input_embed.image.unwrap()); } if input_embed.timestamp != None { e.timestamp(input_embed.timestamp.unwrap()); } if input_embed.title != None { e.title(input_embed.title.unwrap()); } if input_embed.url != None { e.url(input_embed.url.unwrap()); } e }); m }); let _ = message .channel_id .say(&ctx.http, format!("Embed requested by: {}", message.author)); Ok(()) } #[command] fn bottom_rng(ctx: &mut Context, message: &Message, mut args: Args) -> CommandResult { use rand::{rngs::StdRng, SeedableRng}; let num = args.single::().unwrap_or(10); let messages = message .channel_id .messages(&ctx.http, |get| get.before(message.id).limit(num)); if let Err(e) = messages { let _ = message.channel_id.say(&ctx.http, format!("Error: {}", e)); } else { let mut messages = messages.unwrap(); messages.retain(|v| v.author != message.mentions[0]); let mut input = String::new(); for msg in messages { input.push_str(&format!("{} ", msg.content)); } let result: u64 = StdRng::seed_from_u64(calculate_hash(&input)).gen_range(0, 100); let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.title("Bottom RNG") .description(format!("Result: {}", result)) .color(0x800869) }) }); } Ok(()) } #[command] fn ship(ctx: &mut Context, message: &Message, args: Args) -> CommandResult { use rand::{rngs::StdRng, SeedableRng}; // Get input names let names: String = args.rest().trim().to_string(); // Calculate compatibility based on hash let compat: u64 = StdRng::seed_from_u64(calculate_hash(&names)).gen_range(50, 100); // Initialize a bar to display compatibility percentage let mut compbar = String::from("----------"); compbar.insert_str((compat / 10) as usize, ":purple_heart:"); // Convert names to a Vec let names = names .split_whitespace() .map(|x| x.to_owned()) .collect::>(); let shipname: Result = match names.len() { 0 => Err("Invalid input!".to_string()), 1 => Ok(names[0].clone()), _ => { let mut first_halves = String::new(); for name in &names[0..names.len() - 1] { first_halves.push_str(&name[0..name.len() / 2]); } let first_halves = first_halves.as_str(); let last_half = &names[names.len() - 1][(names.len() / 2) + 1..]; Ok(format!("{}{}", first_halves, last_half)) } }; if let Err(e) = shipname { let _ = message.channel_id.say(&ctx.http, e); } else { let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.title(format!("Original names: {}", args.rest().trim())) .description(format!( "Ship name:\n**{}**\nCompatibility: **{}%**\n{}", shipname.unwrap(), compat, compbar )) .color(0xffd1dc) }) }); } Ok(()) } #[command] fn headpat(ctx: &mut Context, message: &Message, args: Args) -> CommandResult { let name = match message.mentions.len() { 0 => args.rest().trim(), _ => message.mentions[0].name.as_str(), }; if let Err(e) = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.title(format!("Sending headpats to **{}**...", name)) .image( "https://i.pinimg.com/originals/83/1a/90/831a903eab6d827dcfd298b9e3196e30.jpg", ) .description("[Source](https://www.pinterest.com/pin/377809856242075277/)") }) }) { let _ = message.channel_id.say(&ctx.http, format!("{:?}", e)); }; Ok(()) } #[command] fn uwu(ctx: &mut Context, message: &Message) -> CommandResult { let images = [ "https://i.redditmedia.com/qDD9W7NJqTAk31y061TuRW9R8qOcCuEmmCWyOsUEavE.png?fit=crop&crop=faces%2Centropy&arh=2&w=640&s=ebdd3f1970b4fe70ccd24a1958e7fc32", "https://www.shitpostbot.com/img/sourceimages/smash-that-mfuckn-uwu-button-57b5aa1de9fe4.jpeg", "https://www.shitpostbot.com/img/sourceimages/fallout-nv-owo-57e586ae15322.jpeg", "https://i.redditmedia.com/-JaK9YW7mPz2S2xBJmXvW4fZ58uGMa4l6GIgYt3dqZg.jpg?fit=crop&crop=faces%2Centropy&arh=2&w=640&s=ebab29a577346b4d18ec914538b69bb4" ]; let num = rand::thread_rng().gen_range(0, images.len()); let _ = message .channel_id .send_message(&ctx.http, |m| m.embed(|e| e.image(images[num]))); Ok(()) } #[command] fn gayculator(ctx: &mut Context, message: &Message, mut args: Args) -> CommandResult { let number_32: i32 = args.single::().unwrap_or(1); let result = if number_32 % 2 == 0 { "much straight" } else { "large gay" }; let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.title("Gayness level:") .description(result) .color(0xffd1dc) }) }); Ok(()) } #[command] fn waffle(ctx: &mut Context, message: &Message) -> CommandResult { let _ = message.channel_id.say(&ctx.http, "h"); Ok(()) } #[command] fn sausage(ctx: &mut Context, message: &Message) -> CommandResult { let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e .title("Dongle!") .image("https://cdn.discordapp.com/attachments/379673147764506624/431546724637736971/image.png") }) }); Ok(()) } /* #[command] #[checks(Server)] fn ad(ctx: &mut Context, message: &Message) -> CommandResult { let _ = message.channel_id.send_message(&ctx.http, |m| m .embed(|e| e .title(":b:ottom text") ***REMOVED*** .thumbnail("https://i.imgur.com/8MU0gqD.png") .color(0x00f3ff)) ); Ok(()) } */ #[command] fn help(ctx: &mut Context, message: &Message) -> CommandResult { let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.title("Availble commands:") .description("All commands are case-insensitive") .fields(vec![ ("owo init", "Introduce me", false), ("owo ping", "Pong", false), ("owo waffle", "stroopwafel owo", false), ("owo sausage", "sosig", false), ("owo help", "Help the fellow humanz!", false), ("owo info", "Show information about me!", false), /*( "owo compare_bot ``bot's name``", "Compare me to other robots!", false, ), */ ( "owo what's this ``word``", "Find a definition of word", false, ), ( "owo embed ``[args]``", "Create an embed from a Toml object", false, ), ("owo ship ``[names]``", "*shipping intensifies*", false), ("owo headpat ``name``", "Headpat someone", false), ("owo owo ``text``", "owoify input text", false), ("\u{200B}", "**Admin commands:**", false), ("owo halt", "kill me", false), ("owo list_srv", "list my servers", false), ("owo host", "Display host info", false), ]) .color(0xffd1dc) }) }); Ok(()) } #[command] fn info(ctx: &mut Context, message: &Message) -> CommandResult { let num = ctx.cache.read().guilds.len(); let _ = message.channel_id.send_message(&ctx.http, |m| m .embed(|e| e .title("Discordinator9000's info:") .description("h") .field("Author:", "#9000 / Agatha", false) .field("Server count:", num , false) .field("Invite:", "[Invite link](https://discordapp.com/api/oauth2/authorize?client_id=470350233419907129&permissions=2048&scope=bot)", false ) .footer(|f| f .text("Written in Rust using Serenity, OwOify and a few other libraries")) .color(0xee657) )); Ok(()) } /* #[command] #[aliases("compare")] fn compare_bot(ctx: &mut Context, message: &Message, mut args: Args) -> CommandResult { //the amiter check if message.author.id != 191948420141809665 { let text: String = args .single::() .unwrap_or_else(|_| "Null".to_string()); if text.to_lowercase().contains("nib") { let _ = message.channel_id.say(&ctx.http, "I am superior to NibBot"); } else if text.to_lowercase().contains("amit") { let _ = message.channel_id.say(&ctx.http, "Amiter is big dumb"); } else if text.to_lowercase().contains("discordinator") { let _ = message.channel_id.say(&ctx.http, "Option<(!, ())>"); } else { let _ = message .channel_id .say(&ctx.http, format!("Me and {} are friends!", text)); } } Ok(()) } */ #[command] #[aliases("what's this")] fn what(ctx: &mut Context, message: &Message, args: Args) -> CommandResult { let text: String = args.rest().trim().to_string(); let defs = &urbandict::get_definitions(&text); match defs { Err(_e) => { let _ = message.channel_id.say(&ctx.http, "Invalid query >w<"); } Ok(v) => { if !v.is_empty() { let def = &v[0]; let _ = message.channel_id.send_message(&ctx.http, |m| { m.embed(|e| { e.title(format!("Author: {}", def.author)).field( "Definition: ", def.definition.replace(|c| c == '[' || c == ']', ""), false, ) }) }); } else { println!("No resuwults"); } } } Ok(()) } #[command] fn owo(ctx: &mut Context, message: &Message, args: Args) -> CommandResult { use owoify::OwOifiable; let input: String = args.rest().trim().to_string(); let _ = message.channel_id.say(&ctx.http, input.owoify()); Ok(()) }