Initial Commit
This commit is contained in:
commit
8055cbb78f
|
@ -0,0 +1,6 @@
|
|||
/target
|
||||
/.vs/*
|
||||
!/.vs/settings.json
|
||||
/logging/
|
||||
Cargo.lock
|
||||
Conf.toml
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"rust.features": [
|
||||
"music"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "rusty_bot"
|
||||
version = "0.1.0"
|
||||
authors = ["oupson"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
music = ["serenity/voice"]
|
||||
|
||||
[dependencies]
|
||||
serenity = { version = "0.9.0-rc.0" }
|
||||
toml = "0.5.6"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
reqwest = "0.10.7"
|
||||
rand = "0.7.3"
|
||||
lazy_static = "1.4.0"
|
||||
async-trait = "0.1.36"
|
||||
tokio = { version = "0.2", features = ["full"] }
|
||||
futures = "0.3"
|
||||
chrono = "0.4.15"
|
||||
serde_json = "1.0"
|
|
@ -0,0 +1,34 @@
|
|||
use crate::commands::Result;
|
||||
use serenity::{model::channel::Message, prelude::Context};
|
||||
|
||||
/// Send a reply to the channel the message was received on.
|
||||
pub(crate) async fn send_reply<'m, S: std::string::ToString>(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
message: S,
|
||||
) -> Result<serenity::model::channel::Message> {
|
||||
Ok(msg.channel_id.say(ctx, message.to_string()).await?)
|
||||
}
|
||||
|
||||
pub(crate) async fn send_splitted_by_lines_in_card<S: std::string::ToString>(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
message: S,
|
||||
) -> Result<()> {
|
||||
let mut buffer = String::from("\x60\x60\x60\n");
|
||||
for line in message.to_string().lines() {
|
||||
if buffer.len() + 4 + line.len() >= 2000 {
|
||||
buffer += "\n\x60\x60\x60";
|
||||
send_reply(ctx, msg, &buffer).await?;
|
||||
buffer = String::from("\x60\x60\x60\n");
|
||||
buffer += line;
|
||||
buffer += "\n";
|
||||
} else {
|
||||
buffer += line;
|
||||
buffer += "\n";
|
||||
}
|
||||
}
|
||||
buffer += "\n\x60\x60\x60";
|
||||
send_reply(ctx, msg, &buffer).await?;
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1,269 @@
|
|||
use crate::{api, debugln};
|
||||
use futures::StreamExt;
|
||||
use serenity::{
|
||||
framework::standard::{
|
||||
macros::{command, group},
|
||||
ArgError, Args, CommandResult,
|
||||
},
|
||||
model::prelude::*,
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
#[group]
|
||||
#[commands(longcode, image, older, ping, invite, infos, error)]
|
||||
pub struct General;
|
||||
|
||||
#[command]
|
||||
pub async fn error(_ctx: &Context, _msg: &Message) -> CommandResult {
|
||||
std::fs::File::open("foobar.txt")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description = "Split a huge code"]
|
||||
#[bucket = "longcode"]
|
||||
pub async fn longcode(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
if let Err(e) = _longcode(ctx, msg, args).await {
|
||||
eprintln!("Error in longcode : {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn _longcode(ctx: &Context, msg: &Message, mut args: Args) -> crate::commands::Result<()> {
|
||||
debugln!("_longcode : {:?}", args);
|
||||
if let Ok(language) = args.single::<String>() {
|
||||
if !msg.attachments.is_empty() {
|
||||
let att = msg.attachments[0].clone();
|
||||
|
||||
let text = reqwest::get(&att.url).await?.text().await?;
|
||||
let header = format!("\x60\x60\x60{}\n", language);
|
||||
let mut buf = header.clone();
|
||||
for line in text.lines() {
|
||||
if buf.len() + 4 + line.len() >= 2000 {
|
||||
buf += "\n\x60\x60\x60";
|
||||
api::send_reply(ctx, msg, buf).await?;
|
||||
buf = header.clone();
|
||||
buf += line;
|
||||
buf += "\n";
|
||||
} else {
|
||||
buf += line;
|
||||
buf += "\n";
|
||||
}
|
||||
}
|
||||
buf += "\n\x60\x60\x60";
|
||||
api::send_reply(ctx, msg, buf).await?;
|
||||
} else {
|
||||
api::send_reply(ctx, msg, "Error, missing code attachement").await?;
|
||||
}
|
||||
} else {
|
||||
api::send_reply(
|
||||
ctx,
|
||||
msg,
|
||||
"Error : Missing language (needed), Example : \n\t\x60?longcode rust\x60",
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description("Print the bot invite link")]
|
||||
async fn invite(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let invite = unsafe { &crate::INVITE_URL };
|
||||
if let Some(invite_url) = invite {
|
||||
if let Err(e) = api::send_reply(ctx, msg, invite_url).await {
|
||||
eprintln!("Error when sending invite : {:?}", e);
|
||||
}
|
||||
} else if let Err(e) =
|
||||
api::send_reply(ctx, msg, "Error : Invite URL is not specified in config").await
|
||||
{
|
||||
eprintln!("Error in invite : {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description("Pong !")]
|
||||
async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let now = std::time::Instant::now();
|
||||
msg.channel_id.say(&ctx.http, "Pong!").await?;
|
||||
let elapsed = now.elapsed();
|
||||
msg.channel_id
|
||||
.say(
|
||||
&ctx.http,
|
||||
format!("Time elapsed : {}ms", elapsed.as_millis()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description = "Find who is the older"]
|
||||
pub async fn older(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
if let Err(e) = _older(ctx, msg, args).await {
|
||||
eprintln!("Error in older : {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn _older(ctx: &Context, msg: &Message, mut args: Args) -> crate::commands::Result<()> {
|
||||
let mut number = 10;
|
||||
if !args.is_empty() {
|
||||
number = args.single::<usize>()?;
|
||||
}
|
||||
|
||||
if let Some(guild) = msg.guild_id {
|
||||
let mut members = guild.members_iter(&ctx).boxed();
|
||||
let mut m: Vec<User> = Vec::with_capacity(number + 1);
|
||||
while let Some(member_result) = members.next().await {
|
||||
match member_result {
|
||||
Ok(member) => {
|
||||
if m.is_empty() {
|
||||
m.push(member.user)
|
||||
} else {
|
||||
let mut added = false;
|
||||
let user = member.user;
|
||||
for i in 0..m.len() {
|
||||
if m[i].created_at() > user.created_at() {
|
||||
m.insert(i, user.clone());
|
||||
if m.len() == number + 1 {
|
||||
m.remove(number);
|
||||
}
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !added && m.len() < number {
|
||||
m.push(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error) => eprintln!("Uh oh! Error: {}", error),
|
||||
}
|
||||
}
|
||||
|
||||
let mut res = String::new();
|
||||
for (i, u) in m.iter().enumerate() {
|
||||
res += &format!("{}. {} ({})\n", i + 1, u.name, u.created_at());
|
||||
}
|
||||
api::send_splitted_by_lines_in_card(&ctx, msg, res).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description = "Print bot infos"]
|
||||
async fn infos(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let res = format!(
|
||||
"{} v{}, by {}\nFeatures : {:?}\nMode : {}",
|
||||
option_env!("CARGO_PKG_NAME").unwrap_or("unknown"),
|
||||
option_env!("CARGO_PKG_VERSION").unwrap_or("unknown"),
|
||||
option_env!("CARGO_PKG_AUTHORS").unwrap_or("unknows"),
|
||||
get_features(),
|
||||
{
|
||||
if cfg!(debug_assertions) {
|
||||
"debug"
|
||||
} else {
|
||||
"release"
|
||||
}
|
||||
}
|
||||
);
|
||||
if let Err(e) = api::send_splitted_by_lines_in_card(ctx, msg, res).await {
|
||||
eprintln!("Error when sending bot infos : {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_features<'m>() -> Vec<&'m str> {
|
||||
let mut res = Vec::new();
|
||||
if cfg!(feature = "music") {
|
||||
res.push("music");
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description = "Image"]
|
||||
#[bucket = "image"]
|
||||
pub async fn image(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
if let Err(e) = _image(ctx, msg, args).await {
|
||||
eprintln!("Error in image : {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn _image(ctx: &Context, msg: &Message, mut args: Args) -> crate::commands::Result<()> {
|
||||
match args.single::<Image>() {
|
||||
Ok(image) => {
|
||||
msg.channel_id
|
||||
.send_message(&ctx.http, |m| {
|
||||
m.embed(|e| {
|
||||
image.embed(e);
|
||||
e
|
||||
});
|
||||
m
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
Err(e) => {
|
||||
if let ArgError::Parse(e) = e {
|
||||
msg.channel_id.say(ctx, e).await?;
|
||||
} else {
|
||||
return Err(Box::new(e));
|
||||
};
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO JSON FILE
|
||||
enum Image {
|
||||
HackerMan(),
|
||||
Koding(),
|
||||
BorrowCheckFailled(),
|
||||
SafetyCheck(),
|
||||
FerisBurn(),
|
||||
EmploiStable(),
|
||||
}
|
||||
|
||||
impl Image {
|
||||
fn embed(&self, embed: &mut serenity::builder::CreateEmbed) {
|
||||
match self {
|
||||
Image::HackerMan() => {
|
||||
embed.image("https://images-wixmp-ed30a86b8c4ca887773594c2.wixmp.com/f/d8328f77-2bec-4a20-9483-ea76dd62985e/dan31sc-80f18518-0ef0-4bdc-9c0a-11c9e62b769f.png?token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ1cm46YXBwOjdlMGQxODg5ODIyNjQzNzNhNWYwZDQxNWVhMGQyNmUwIiwic3ViIjoidXJuOmFwcDo3ZTBkMTg4OTgyMjY0MzczYTVmMGQ0MTVlYTBkMjZlMCIsImF1ZCI6WyJ1cm46c2VydmljZTpmaWxlLmRvd25sb2FkIl0sIm9iaiI6W1t7InBhdGgiOiIvZi9kODMyOGY3Ny0yYmVjLTRhMjAtOTQ4My1lYTc2ZGQ2Mjk4NWUvZGFuMzFzYy04MGYxODUxOC0wZWYwLTRiZGMtOWMwYS0xMWM5ZTYyYjc2OWYucG5nIn1dXX0.upSIXFazVoJxWpPPle4gmgwfJgx7Gvc603Sbbe-KJB0");
|
||||
}
|
||||
Image::Koding() => {
|
||||
embed.image("https://i.kym-cdn.com/photos/images/original/001/739/593/45d.jpg");
|
||||
}
|
||||
Image::BorrowCheckFailled() => {
|
||||
embed.image("https://cdn.discordapp.com/attachments/592452150517301248/721104919058448434/brrowchk.jpg");
|
||||
}
|
||||
Image::SafetyCheck() => {
|
||||
embed.image("https://cdn.discordapp.com/attachments/592452150517301248/695056442704527451/safety-check.png");
|
||||
}
|
||||
Image::FerisBurn() => {
|
||||
embed.image("https://oupson.fr/rusty_bot/static_files/ferrisburn.gif");
|
||||
}
|
||||
Image::EmploiStable() => {
|
||||
embed.image("https://i.imgur.com/q5scLGs.jpg");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Image {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"hackerman" => Ok(Self::HackerMan()),
|
||||
"koding" => Ok(Self::Koding()),
|
||||
"borrowcheckfailled" => Ok(Self::BorrowCheckFailled()),
|
||||
"safetycheck" => Ok(Self::SafetyCheck()),
|
||||
"ferrisburn" => Ok(Self::FerisBurn()),
|
||||
"emploistable" => Ok(Self::EmploiStable()),
|
||||
_ => Err(format!("{} is not an image", s)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
pub(crate) mod admin;
|
||||
pub(crate) mod general;
|
||||
pub(crate) mod roulette;
|
||||
|
||||
#[cfg(feature = "music")]
|
||||
pub(crate) mod music;
|
||||
|
||||
pub(crate) type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
@ -0,0 +1,233 @@
|
|||
use serenity::{
|
||||
client::{bridge::voice::ClientVoiceManager, Context},
|
||||
framework::standard::{
|
||||
macros::{command, group},
|
||||
Args, CommandResult,
|
||||
},
|
||||
model::{channel::Message, misc::Mentionable},
|
||||
prelude::*,
|
||||
voice, Result as SerenityResult,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) struct VoiceManager;
|
||||
|
||||
impl TypeMapKey for VoiceManager {
|
||||
type Value = Arc<Mutex<ClientVoiceManager>>;
|
||||
}
|
||||
|
||||
#[group]
|
||||
#[commands(join, leave, play, stop)]
|
||||
struct Music;
|
||||
|
||||
#[command]
|
||||
#[description("Join the channel you are connected")]
|
||||
async fn join(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let guild = match msg.guild(&ctx.cache).await {
|
||||
Some(guild) => guild,
|
||||
None => {
|
||||
check_msg(msg.channel_id.say(&ctx.http, "DMs not supported").await);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let guild_id = guild.id;
|
||||
|
||||
let channel_id = guild
|
||||
.voice_states
|
||||
.get(&msg.author.id)
|
||||
.and_then(|voice_state| voice_state.channel_id);
|
||||
|
||||
let connect_to = match channel_id {
|
||||
Some(channel) => channel,
|
||||
None => {
|
||||
check_msg(msg.reply(ctx, "Not in a voice channel").await);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let manager_lock = ctx
|
||||
.data
|
||||
.read()
|
||||
.await
|
||||
.get::<VoiceManager>()
|
||||
.cloned()
|
||||
.expect("Expected VoiceManager in TypeMap.");
|
||||
let mut manager = manager_lock.lock().await;
|
||||
|
||||
if manager.join(guild_id, connect_to).is_some() {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, &format!("Joined {}", connect_to.mention()))
|
||||
.await,
|
||||
);
|
||||
} else {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Error joining the channel")
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description("Leave the channel you are connected")]
|
||||
async fn leave(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let guild_id = match ctx
|
||||
.cache
|
||||
.guild_channel_field(msg.channel_id, |channel| channel.guild_id)
|
||||
.await
|
||||
{
|
||||
Some(id) => id,
|
||||
None => {
|
||||
check_msg(msg.channel_id.say(&ctx.http, "DMs not supported").await);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let manager_lock = ctx
|
||||
.data
|
||||
.read()
|
||||
.await
|
||||
.get::<VoiceManager>()
|
||||
.cloned()
|
||||
.expect("Expected VoiceManager in TypeMap.");
|
||||
let mut manager = manager_lock.lock().await;
|
||||
let has_handler = manager.get(guild_id).is_some();
|
||||
|
||||
if has_handler {
|
||||
manager.remove(guild_id);
|
||||
|
||||
check_msg(msg.channel_id.say(&ctx.http, "Left voice channel").await);
|
||||
} else {
|
||||
check_msg(msg.reply(ctx, "Not in a voice channel").await);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description("Play a music (require an url)")]
|
||||
async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
|
||||
let url = match args.single::<String>() {
|
||||
Ok(url) => url,
|
||||
Err(_) => {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Must provide a URL to a video or audio")
|
||||
.await,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if !url.starts_with("http") {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Must provide a valid URL")
|
||||
.await,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let guild_id = match ctx.cache.guild_channel(msg.channel_id).await {
|
||||
Some(channel) => channel.guild_id,
|
||||
None => {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Error finding channel info")
|
||||
.await,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let manager_lock = ctx
|
||||
.data
|
||||
.read()
|
||||
.await
|
||||
.get::<VoiceManager>()
|
||||
.cloned()
|
||||
.expect("Expected VoiceManager in TypeMap.");
|
||||
let mut manager = manager_lock.lock().await;
|
||||
|
||||
if let Some(handler) = manager.get_mut(guild_id) {
|
||||
let source = match voice::ytdl(&url).await {
|
||||
Ok(source) => source,
|
||||
Err(why) => {
|
||||
println!("Err starting source: {:?}", why);
|
||||
|
||||
check_msg(msg.channel_id.say(&ctx.http, "Error sourcing ffmpeg").await);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
handler.stop();
|
||||
handler.play(source);
|
||||
|
||||
check_msg(msg.channel_id.say(&ctx.http, "Playing song").await);
|
||||
} else {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Not in a voice channel to play in")
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description("Stop the music")]
|
||||
async fn stop(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
let guild_id = match ctx.cache.guild_channel(msg.channel_id).await {
|
||||
Some(channel) => channel.guild_id,
|
||||
None => {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Error finding channel info")
|
||||
.await,
|
||||
);
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let manager_lock = ctx
|
||||
.data
|
||||
.read()
|
||||
.await
|
||||
.get::<VoiceManager>()
|
||||
.cloned()
|
||||
.expect("Expected VoiceManager in TypeMap.");
|
||||
let mut manager = manager_lock.lock().await;
|
||||
|
||||
if let Some(handler) = manager.get_mut(guild_id) {
|
||||
handler.stop();
|
||||
|
||||
check_msg(msg.channel_id.say(&ctx.http, "Stopping song").await);
|
||||
} else {
|
||||
check_msg(
|
||||
msg.channel_id
|
||||
.say(&ctx.http, "Not in a voice channel to play in")
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks that a message successfully sent; if not, then logs why to stderr.
|
||||
fn check_msg(result: SerenityResult<Message>) {
|
||||
if let Err(why) = result {
|
||||
eprintln!("Error sending message: {:?}", why);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
use crate::{api, commands, debugln};
|
||||
use rand::Rng;
|
||||
use serenity::{
|
||||
framework::standard::{
|
||||
macros::{command, group},
|
||||
Args, CommandResult,
|
||||
},
|
||||
model::prelude::*,
|
||||
prelude::*,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct BulletsContainer;
|
||||
|
||||
impl TypeMapKey for BulletsContainer {
|
||||
type Value = HashMap<u64, u8>;
|
||||
}
|
||||
|
||||
#[group]
|
||||
#[default_command(shot)]
|
||||
#[prefix("roulette")]
|
||||
#[commands(reload, shot)]
|
||||
struct Roulette;
|
||||
|
||||
#[command]
|
||||
#[description = "PRESS THE TRIGGER !"]
|
||||
#[bucket = "roulette"]
|
||||
async fn shot(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
let _message = args.message().trim_end();
|
||||
if _message == "shot" || _message == "" {
|
||||
if let Err(e) = _shot(ctx, msg).await {
|
||||
eprintln!("{}", e);
|
||||
}
|
||||
} else if let Err(e) = api::send_reply(
|
||||
ctx,
|
||||
msg,
|
||||
format!("Error : {} is not a valid argument", args.message()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
eprintln!("Error : {:?}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn _shot(ctx: &Context, msg: &Message) -> commands::Result<()> {
|
||||
let mut data = ctx.data.write().await;
|
||||
let bullets_map = data
|
||||
.get_mut::<BulletsContainer>()
|
||||
.expect("Expected CommandCounter in TypeMap.");
|
||||
let bullets = bullets_map.entry(msg.author.id.0).or_insert(6);
|
||||
if rand::thread_rng().gen_range(0, *bullets) == 0 {
|
||||
api::send_reply(ctx, &msg, "BOOM !").await?;
|
||||
*bullets = 6;
|
||||
} else {
|
||||
*bullets -= 1;
|
||||
api::send_reply(
|
||||
&ctx,
|
||||
&msg,
|
||||
format!("Click ! bullets remaining : {}", bullets),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
debugln!("{:?}", bullets_map);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[description = "Reload"]
|
||||
#[bucket = "roulette"]
|
||||
async fn reload(ctx: &Context, msg: &Message) -> CommandResult {
|
||||
if let Err(e) = _reload(ctx, msg).await {
|
||||
eprintln!("{}", e);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn _reload(ctx: &Context, msg: &Message) -> commands::Result<()> {
|
||||
let mut data = ctx.data.write().await;
|
||||
let bullets_map = data
|
||||
.get_mut::<BulletsContainer>()
|
||||
.expect("Expected CommandCounter in TypeMap.");
|
||||
bullets_map.insert(msg.author.id.0, 6);
|
||||
msg.react(ctx, ReactionType::Unicode(String::from("✅")))
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Conf {
|
||||
pub(crate) bot: Bot,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub(crate) struct Bot {
|
||||
pub(crate) token: String,
|
||||
pub(crate) log_attachments: Option<bool>,
|
||||
pub(crate) invite_url: Option<String>,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#[macro_export]
|
||||
macro_rules! debugln {
|
||||
($($arg:tt)*) => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!($($arg)*);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -0,0 +1,303 @@
|
|||
use crate::commands::{
|
||||
general::GENERAL_GROUP,
|
||||
roulette::{BulletsContainer, ROULETTE_GROUP},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use serenity::{
|
||||
framework::standard::{
|
||||
help_commands,
|
||||
macros::{help, hook},
|
||||
Args, CommandGroup, CommandResult, DispatchError, HelpOptions, StandardFramework, CommandError,
|
||||
},
|
||||
http::Http,
|
||||
model::prelude::*,
|
||||
prelude::*,
|
||||
};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fs::{self},
|
||||
io::Result as IoResult,
|
||||
path::Path,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
use serde_json::Value;
|
||||
|
||||
#[cfg(feature = "music")]
|
||||
use crate::commands::music::{VoiceManager, MUSIC_GROUP};
|
||||
|
||||
mod api;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod macros;
|
||||
mod presence;
|
||||
|
||||
const PREFIX: &str = "?";
|
||||
static mut LOG_ATTACHMENTS: bool = false;
|
||||
pub(crate) static mut INVITE_URL: Option<String> = None;
|
||||
|
||||
//TODO CLAP FOR CLI
|
||||
#[tokio::main]
|
||||
async fn main() -> IoResult<()> {
|
||||
let conf: config::Conf = toml::from_str(&std::fs::read_to_string("Conf.toml")?)?;
|
||||
debugln!("conf : {:?}", conf);
|
||||
|
||||
let token = conf.bot.token;
|
||||
|
||||
if conf.bot.log_attachments.unwrap_or(false) {
|
||||
unsafe {
|
||||
LOG_ATTACHMENTS = true;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(url) = conf.bot.invite_url {
|
||||
unsafe {
|
||||
INVITE_URL = Some(url);
|
||||
}
|
||||
}
|
||||
|
||||
debugln!("Log attachments : {}", unsafe { LOG_ATTACHMENTS });
|
||||
|
||||
let dir = Path::new("logging");
|
||||
if !dir.exists() {
|
||||
fs::create_dir(dir)?;
|
||||
}
|
||||
|
||||
let http = Http::new_with_token(&token);
|
||||
|
||||
// We will fetch your bot's owners and id
|
||||
let (owners, bot_id) = match http.get_current_application_info().await {
|
||||
Ok(info) => {
|
||||
let mut owners = HashSet::new();
|
||||
owners.insert(info.owner.id);
|
||||
|
||||
(owners, info.id)
|
||||
}
|
||||
Err(why) => panic!("Could not access application info: {:?}", why),
|
||||
};
|
||||
|
||||
debugln!("Owners : {:?}", owners);
|
||||
|
||||
let mut framework = StandardFramework::new()
|
||||
.configure(|c| {
|
||||
c.with_whitespace(true)
|
||||
.on_mention(Some(bot_id))
|
||||
.prefix(PREFIX)
|
||||
// In this case, if "," would be first, a message would never
|
||||
// be delimited at ", ", forcing you to trim your arguments if you
|
||||
// want to avoid whitespaces at the start of each.
|
||||
.delimiters(vec![", ", ","])
|
||||
// Sets the bot's owners. These will be used for commands that
|
||||
// are owners only.
|
||||
.owners(owners)
|
||||
})
|
||||
// Set a function that's called whenever an attempted command-call's
|
||||
// command could not be found.
|
||||
.unrecognised_command(unknown_command)
|
||||
// Set a function that's called whenever a command's execution didn't complete for one
|
||||
// reason or another. For example, when a user has exceeded a rate-limit or a command
|
||||
// can only be performed by the bot owner.
|
||||
.on_dispatch_error(dispatch_error)
|
||||
.after(after_hook)
|
||||
.help(&MY_HELP)
|
||||
.group(&GENERAL_GROUP)
|
||||
.group(&ROULETTE_GROUP);
|
||||
|
||||
#[cfg(feature = "music")]
|
||||
{
|
||||
framework = framework.group(&MUSIC_GROUP);
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
// Set a function that's called whenever a message is not a command.
|
||||
framework = framework.normal_message(normal_message)
|
||||
}
|
||||
|
||||
let mut client = Client::new(&token)
|
||||
.event_handler(Messages {})
|
||||
.framework(framework)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
let mut data = client.data.write().await;
|
||||
data.insert::<BulletsContainer>(HashMap::default());
|
||||
#[cfg(feature = "music")]
|
||||
{
|
||||
data.insert::<VoiceManager>(std::sync::Arc::clone(&client.voice_manager));
|
||||
}
|
||||
}
|
||||
|
||||
client.start().await.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct Messages {}
|
||||
|
||||
impl Messages {
|
||||
async fn _reaction_add(
|
||||
&self,
|
||||
ctx: Context,
|
||||
reaction: Reaction,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
// TODO
|
||||
let message: Message = reaction.message(&ctx.http).await?;
|
||||
if message.is_own(&ctx).await && message.content.starts_with("Do you want to take the gun")
|
||||
{
|
||||
debugln!("My own garbage !");
|
||||
let user = reaction.user(&ctx.http).await?;
|
||||
if user == message.mentions[1] {
|
||||
message.delete(&ctx.http).await?;
|
||||
if reaction.emoji
|
||||
== serenity::model::channel::ReactionType::Unicode(String::from("✅"))
|
||||
{
|
||||
let mut data = ctx.data.write().await;
|
||||
let bullets_map = data
|
||||
.get_mut::<BulletsContainer>()
|
||||
.expect("Expected CommandCounter in TypeMap.");
|
||||
if bullets_map.contains_key(&message.mentions[0].id.0) {
|
||||
let bullet_count =
|
||||
bullets_map.remove(&message.mentions[0].id.0).unwrap_or(6);
|
||||
bullets_map.insert(message.mentions[1].id.0, bullet_count);
|
||||
message.channel_id.say(&ctx, "Done").await?;
|
||||
} else {
|
||||
message
|
||||
.channel_id
|
||||
.say(&ctx, "Error : Your gun is empty")
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Messages {
|
||||
async fn reaction_add(&self, _ctx: Context, reaction: Reaction) {
|
||||
debugln!("Reaction added : {:?}", reaction);
|
||||
/*
|
||||
if let Err(e) = self._reaction_add(ctx, reaction).await {
|
||||
eprintln!("{}", e);
|
||||
}*/
|
||||
}
|
||||
|
||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||
println!("{} connected to discord", ready.user.name);
|
||||
|
||||
ctx.set_presence(Some(Activity::listening("?")), OnlineStatus::Online)
|
||||
.await;
|
||||
|
||||
let ctx_clone = ctx.clone();
|
||||
tokio::spawn(async move {
|
||||
let delay = Duration::from_secs(5);
|
||||
let mut presence_generator = presence::Presences::new();
|
||||
while let Some(act) = presence_generator.next(&ctx_clone).await {
|
||||
ctx_clone
|
||||
.set_presence(Some(act), OnlineStatus::Online)
|
||||
.await;
|
||||
tokio::time::delay_for(delay).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn message(&self, _ctx: Context, new_message: Message) {
|
||||
if unsafe { LOG_ATTACHMENTS } && !new_message.attachments.is_empty() {
|
||||
for att in new_message.attachments {
|
||||
if let Err(e) = download_to_log(att).await {
|
||||
eprintln!("Error while downloading to log : {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn unknown(&self, _ctx: Context, name: String, raw: Value) {
|
||||
println!("Unknown event : {}, {:?}", name, raw);
|
||||
}
|
||||
}
|
||||
|
||||
async fn download_to_log(attachment: Attachment) -> commands::Result<()> {
|
||||
debugln!("Download_to_log : {:?}", attachment);
|
||||
let path = Path::new("logging").join(format!("{}-{}", attachment.id, attachment.filename));
|
||||
let content = reqwest::get(&attachment.url).await?.bytes().await?;
|
||||
let mut file = File::create(path).await?;
|
||||
file.write_all(&content).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[hook]
|
||||
async fn unknown_command(_ctx: &Context, _msg: &Message, unknown_command_name: &str) {
|
||||
println!("Could not find command named '{}'", unknown_command_name);
|
||||
}
|
||||
|
||||
#[hook]
|
||||
async fn normal_message(_ctx: &Context, msg: &Message) {
|
||||
println!("Message is not a command '{}'", msg.content);
|
||||
}
|
||||
|
||||
#[hook]
|
||||
async fn dispatch_error(ctx: &Context, msg: &Message, error: DispatchError) {
|
||||
debugln!("Dispatch error : {:?}", error);
|
||||
if let DispatchError::Ratelimited(seconds) = error {
|
||||
let _ = msg
|
||||
.channel_id
|
||||
.say(
|
||||
&ctx.http,
|
||||
&format!("Try this again in {} seconds.", seconds),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
#[hook]
|
||||
async fn after_hook(_: &Context, _: &Message, cmd_name: &str, error: Result<(), CommandError>) {
|
||||
// Print out an error if it happened
|
||||
if let Err(why) = error {
|
||||
println!("Error in {}: {:?}", cmd_name, why);
|
||||
}
|
||||
}
|
||||
|
||||
// The framework provides two built-in help commands for you to use.
|
||||
// But you can also make your own customized help command that forwards
|
||||
// to the behaviour of either of them.
|
||||
#[help]
|
||||
// This replaces the information that a user can pass
|
||||
// a command-name as argument to gain specific information about it.
|
||||
#[individual_command_tip = "Hello!\n\
|
||||
If you want more information about a specific command, just pass the command as argument."]
|
||||
// Some arguments require a `{}` in order to replace it with contextual information.
|
||||
// In this case our `{}` refers to a command's name.
|
||||
#[command_not_found_text = "Could not find: \x60{}\x60."]
|
||||
// Define the maximum Levenshtein-distance between a searched command-name
|
||||
// and commands. If the distance is lower than or equal the set distance,
|
||||
// it will be displayed as a suggestion.
|
||||
// Setting the distance to 0 will disable suggestions.
|
||||
#[max_levenshtein_distance(3)]
|
||||
// When you use sub-groups, Serenity will use the `indention_prefix` to indicate
|
||||
// how deeply an item is indented.
|
||||
// The default value is "-", it will be changed to "+".
|
||||
#[indention_prefix = "+"]
|
||||
// On another note, you can set up the help-menu-filter-behaviour.
|
||||
// Here are all possible settings shown on all possible options.
|
||||
// First case is if a user lacks permissions for a command, we can hide the command.
|
||||
#[lacking_permissions = "Hide"]
|
||||
// If the user is nothing but lacking a certain role, we just display it hence our variant is `Nothing`.
|
||||
#[lacking_role = "Nothing"]
|
||||
// The last `enum`-variant is `Strike`, which ~~strikes~~ a command.
|
||||
#[wrong_channel = "Strike"]
|
||||
// Serenity will automatically analyse and generate a hint/tip explaining the possible
|
||||
// cases of ~~strikethrough-commands~~, but only if
|
||||
// `strikethrough_commands_tip_{dm, guild}` aren't specified.
|
||||
// If you pass in a value, it will be displayed instead.
|
||||
async fn my_help(
|
||||
context: &Context,
|
||||
msg: &Message,
|
||||
args: Args,
|
||||
help_options: &'static HelpOptions,
|
||||
groups: &[&'static CommandGroup],
|
||||
owners: HashSet<UserId>,
|
||||
) -> CommandResult {
|
||||
let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
use serenity::{model::prelude::*, prelude::*};
|
||||
|
||||
const PRESENCE_COUNT: usize = 3;
|
||||
|
||||
pub(crate) struct Presences {
|
||||
index: usize,
|
||||
}
|
||||
|
||||
impl Presences {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self { index: 0 }
|
||||
}
|
||||
|
||||
pub(crate) async fn next(&mut self, ctx: &Context) -> Option<Activity> {
|
||||
let res: Activity = match self.index {
|
||||
0 => Activity::listening("?"),
|
||||
1 => Activity::listening(&format!("{} servers", ctx.cache.guild_count().await)),
|
||||
2 => Activity::playing("praising the borrow checker"),
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
self.index += 1;
|
||||
if self.index >= PRESENCE_COUNT {
|
||||
self.index = 0;
|
||||
}
|
||||
Some(res)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue