From 1a8a6eca29afe2178c6cf17abce9ab7e229ca26c Mon Sep 17 00:00:00 2001 From: Nathan Perry Date: Thu, 24 Jan 2019 20:03:25 -0500 Subject: improve imports further and fix compile warnings --- src/commands/meme.rs | 32 +-- src/commands/mod.rs | 15 +- src/commands/playback/mod.rs | 7 +- src/commands/roll.rs | 13 +- src/commands/sound.rs | 17 +- src/db/mod.rs | 257 +++++++++++------------ src/db/models.rs | 471 ++++++++++++++++++++++--------------------- src/main.rs | 7 +- 8 files changed, 421 insertions(+), 398 deletions(-) (limited to 'src') diff --git a/src/commands/meme.rs b/src/commands/meme.rs index de64ad5..6d50349 100644 --- a/src/commands/meme.rs +++ b/src/commands/meme.rs @@ -1,21 +1,29 @@ use std::sync::RwLock; +use diesel::PgConnection; +use failure::Error; +use lazy_static::lazy_static; +use rand::{Rng, thread_rng}; use serenity::{ - http::AttachmentType, builder::CreateMessage, framework::standard::Args, + http::AttachmentType, + model::channel::Message, + prelude::*, }; -use rand::{thread_rng, Rng}; -use diesel::PgConnection; -use failure::Error; -use lazy_static::lazy_static; - -use super::*; -use super::playback::CtxExt; - -use crate::db::*; -use crate::Result; +use crate::{ + commands::{ + playback::{ + CtxExt, + PlayArgs, + PlayQueue, + }, + send, + }, + db::*, + Result, +}; lazy_static! { static ref LAST_MEME: RwLock> = RwLock::new(None); @@ -28,7 +36,7 @@ fn update_meme(meme: &Meme) -> Result<()> { Ok(()) } -pub fn meme(ctx: &mut Context, msg: &Message, mut args: Args) -> Result<()> { +pub fn meme(ctx: &mut Context, msg: &Message, args: Args) -> Result<()> { if args.len() == 0 { return rand_meme(ctx, msg); } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index db304e3..f2e577b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,23 +4,25 @@ use std::{ }; use serenity::{ - prelude::*, framework::StandardFramework, model::{ channel::Message, - id::MessageId, + id::ChannelId, }, + prelude::*, }; use crate::{must_env_lookup, Result, TARGET_GUILD_ID}; +#[cfg(feature = "diesel")] +pub use self::meme::*; +pub use self::playback::*; +pub use self::sound::*; + mod playback; mod sound; mod roll; -pub use self::sound::*; -pub use self::playback::*; - pub fn register_commands(f: StandardFramework) -> StandardFramework { let f: StandardFramework = register_db(f); f @@ -83,9 +85,6 @@ pub fn register_commands(f: StandardFramework) -> StandardFramework { #[cfg(feature = "diesel")] mod meme; -#[cfg(feature = "diesel")] -pub use self::meme::*; - #[cfg(feature = "diesel")] fn register_db(f: StandardFramework) -> StandardFramework { f diff --git a/src/commands/playback/mod.rs b/src/commands/playback/mod.rs index efb2553..83732f2 100644 --- a/src/commands/playback/mod.rs +++ b/src/commands/playback/mod.rs @@ -1,11 +1,12 @@ use either::{Left, Right}; use serenity::{ - voice::{LockedAudio, ytdl}, - model::id::ChannelId, framework::standard::Args, + model::id::ChannelId, + voice::{LockedAudio, ytdl}, }; use super::*; + pub use self::types::*; mod types; @@ -24,7 +25,7 @@ impl CtxExt for Context { fn users_listening(&self) -> Result { let channel_id = ChannelId(must_env_lookup::("VOICE_CHANNEL")); - let channel = channel_id.get()?; + let channel = channel_id.to_channel()?; let res = channel.guild() .and_then(|ch| ch.read().guild()) .map(|g| (&g.read().voice_states) diff --git a/src/commands/roll.rs b/src/commands/roll.rs index 217b3a4..b3f7a38 100644 --- a/src/commands/roll.rs +++ b/src/commands/roll.rs @@ -1,14 +1,15 @@ +use rand::prelude::*; +use regex::Regex; use serenity::{ - prelude::*, framework::standard::Args, model::channel::Message, + prelude::*, }; -use regex::Regex; -use rand::prelude::*; -use crate::Result; - -use super::send; +use crate::{ + commands::send, + Result, +}; lazy_static! { static ref ROLL_REGEX: Regex = Regex::new(r"([0-9]+)?(?:d([0-9]+)(?:\s+\+\s+([0-9]+))?)") diff --git a/src/commands/sound.rs b/src/commands/sound.rs index 62db0e3..7465e2e 100644 --- a/src/commands/sound.rs +++ b/src/commands/sound.rs @@ -1,5 +1,18 @@ -use super::*; -use serenity::framework::standard::Args; +use serenity::{ + framework::standard::Args, + model::channel::Message, + prelude::*, + +}; + +use crate::{ + commands::{ + playback::{PlayQueue, VoiceManager}, + send, + }, + Result, + TARGET_GUILD_ID, +}; pub const DEFAULT_VOLUME: f32 = 0.10; diff --git a/src/db/mod.rs b/src/db/mod.rs index 722c72d..1ff05ce 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,128 +1,129 @@ -use std::{ - env, - convert::AsRef, -}; - -use diesel::{ - prelude::*, - r2d2::{ConnectionManager, ManageConnection}, - NotFound, -}; - -use super::{Result, Error}; -pub use self::models::*; -use self::schema::*; - -mod schema; -mod models; - -lazy_static! { - static ref DB_URL: String = env::var("DATABASE_URL").expect("no database url in environment").into(); - static ref CONN_MGR: ConnectionManager = ConnectionManager::new(DB_URL.clone()); -} - -pub fn connection() -> Result { - CONN_MGR.connect().map_err(Error::from) -} - -pub fn find_meme>(conn: &PgConnection, search: T) -> Result { - use diesel::dsl::sql; - use diesel::sql_types::Text; - - let search = search.as_ref(); - - // TODO: check for injection - let mut meme = memes::table - .filter(memes::title.eq(search)) - .limit(1) - .first::(conn); - - if let Err(NotFound) = meme { - let format_search = format!("%{}%", search); - - meme = memes::table - .filter(memes::title.ilike(&format_search).or(sql("content ILIKE ").bind::(&format_search))) - .limit(1) - .first::(conn); - } - - meme - .map_err(Error::from) -} - -pub fn delete_meme>(conn: &PgConnection, search: T, deleted_by: u64) -> Result<()> { - conn.transaction::<(), Error, _>(|| { - let deleted = memes::table - .filter(memes::title.eq(search.as_ref())) - .first::(conn)?; - - ::diesel::delete(memes::table) - .filter(memes::id.eq(deleted.id)) - .execute(conn)?; - - if let Some(image_id) = deleted.image_id { - let count = memes::table - .filter(memes::image_id.eq(image_id)) - .count() - .execute(conn)?; - - if count == 0 { - ::diesel::delete(images::table) - .filter(images::id.eq(image_id)) - .execute(conn)?; - } - } - - if let Some(audio_id) = deleted.audio_id { - let count = memes::table - .select(::diesel::dsl::count_star()) - .filter(memes::audio_id.eq(audio_id)) - .execute(conn)?; - - if count == 0 { - ::diesel::delete(audio::table) - .filter(audio::id.eq(audio_id)) - .execute(conn)?; - } - } - - let tombstone = NewTombstone { - deleted_by: deleted_by as i64, - metadata_id: deleted.metadata_id, - meme_id: deleted.id, - }; - - let _ = ::diesel::insert_into(tombstones::table) - .values(&tombstone) - .execute(conn)?; - - Ok(()) - }) -} - -pub fn rand_text(conn: &PgConnection) -> Result { - memes::table - .filter(memes::content.is_not_null()) - .order(random.desc()) - .first::(conn) - .map_err(Error::from) -} - -pub fn rand_image(conn: &PgConnection) -> Result { - memes::table - .filter(memes::image_id.is_not_null()) - .order(random.desc()) - .first::(conn) - .map_err(Error::from) -} - -pub fn rand_audio(conn: &PgConnection) -> Result { - memes::table - .filter(memes::audio_id.is_not_null()) - .order(random.desc()) - .first::(conn) - .map_err(Error::from) -} - -use diesel::sql_types; -no_arg_sql_function!(random, sql_types::Double, "SQL random() function"); +use std::{ + convert::AsRef, + env, +}; + +use diesel::{ + NotFound, + prelude::*, + r2d2::{ConnectionManager, ManageConnection}, +}; +use diesel::sql_types; + +use crate::{Error, Result}; + +pub use self::models::*; +use self::schema::*; + +mod schema; +mod models; + +lazy_static! { + static ref DB_URL: String = env::var("DATABASE_URL").expect("no database url in environment").into(); + static ref CONN_MGR: ConnectionManager = ConnectionManager::new(DB_URL.clone()); +} + +pub fn connection() -> Result { + CONN_MGR.connect().map_err(Error::from) +} + +pub fn find_meme>(conn: &PgConnection, search: T) -> Result { + use diesel::dsl::sql; + use diesel::sql_types::Text; + + let search = search.as_ref(); + + // TODO: check for injection + let mut meme = memes::table + .filter(memes::title.eq(search)) + .limit(1) + .first::(conn); + + if let Err(NotFound) = meme { + let format_search = format!("%{}%", search); + + meme = memes::table + .filter(memes::title.ilike(&format_search).or(sql("content ILIKE ").bind::(&format_search))) + .limit(1) + .first::(conn); + } + + meme + .map_err(Error::from) +} + +pub fn delete_meme>(conn: &PgConnection, search: T, deleted_by: u64) -> Result<()> { + conn.transaction::<(), Error, _>(|| { + let deleted = memes::table + .filter(memes::title.eq(search.as_ref())) + .first::(conn)?; + + ::diesel::delete(memes::table) + .filter(memes::id.eq(deleted.id)) + .execute(conn)?; + + if let Some(image_id) = deleted.image_id { + let count = memes::table + .filter(memes::image_id.eq(image_id)) + .count() + .execute(conn)?; + + if count == 0 { + ::diesel::delete(images::table) + .filter(images::id.eq(image_id)) + .execute(conn)?; + } + } + + if let Some(audio_id) = deleted.audio_id { + let count = memes::table + .select(::diesel::dsl::count_star()) + .filter(memes::audio_id.eq(audio_id)) + .execute(conn)?; + + if count == 0 { + ::diesel::delete(audio::table) + .filter(audio::id.eq(audio_id)) + .execute(conn)?; + } + } + + let tombstone = NewTombstone { + deleted_by: deleted_by as i64, + metadata_id: deleted.metadata_id, + meme_id: deleted.id, + }; + + let _ = ::diesel::insert_into(tombstones::table) + .values(&tombstone) + .execute(conn)?; + + Ok(()) + }) +} + +pub fn rand_text(conn: &PgConnection) -> Result { + memes::table + .filter(memes::content.is_not_null()) + .order(random.desc()) + .first::(conn) + .map_err(Error::from) +} + +pub fn rand_image(conn: &PgConnection) -> Result { + memes::table + .filter(memes::image_id.is_not_null()) + .order(random.desc()) + .first::(conn) + .map_err(Error::from) +} + +pub fn rand_audio(conn: &PgConnection) -> Result { + memes::table + .filter(memes::audio_id.is_not_null()) + .order(random.desc()) + .first::(conn) + .map_err(Error::from) +} + +no_arg_sql_function!(random, sql_types::Double, "SQL random() function"); diff --git a/src/db/models.rs b/src/db/models.rs index 659a38a..67aa1ca 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -1,234 +1,237 @@ -use chrono::naive::NaiveDateTime; -use diesel::prelude::*; - -use super::schema::*; -use crate::{Result, Error}; - -#[derive(Queryable, Identifiable, PartialEq, Debug)] -#[table_name="memes"] -pub struct Meme { - pub id: i32, - pub title: String, - pub content: Option, - pub image_id: Option, - pub audio_id: Option, - pub metadata_id: i32, -} - -impl Meme { - pub fn image(&self, conn: &PgConnection) -> Option> { - self.image_id.map(|x: i32| images::table.filter(images::id.eq(x)).first(conn).map_err(Error::from)) - } - - pub fn audio(&self, conn: &PgConnection) -> Option> { - self.audio_id.map(|x: i32| audio::table.filter(audio::id.eq(x)).first(conn).map_err(Error::from)) - } - - pub fn find(conn: &PgConnection, id: i32) -> Result { - memes::table.find(id).get_result(conn).map_err(Error::from) - } -} - -#[derive(Insertable, PartialEq, Debug)] -#[table_name="memes"] -pub struct NewMeme { - pub title: String, - pub content: Option, - pub image_id: Option, - pub audio_id: Option, - pub metadata_id: i32, -} - -impl NewMeme { - pub fn save(mut self, conn: &PgConnection, by_user: u64) -> Result { - let metadata = Metadata::create(conn, by_user)?; - - self.metadata_id = metadata.id; - - ::diesel::insert_into(memes::table) - .values(&self) - .get_result::(conn) - .map_err(Error::from) - } -} - - -#[derive(Queryable, Identifiable, PartialEq, Debug)] -#[table_name="audio"] -pub struct Audio { - pub id: i32, - pub data: Vec, - pub metadata_id: i32, - pub data_hash: Vec, -} - -impl Audio { - pub fn create(conn: &PgConnection, data: Vec, by_user: u64) -> Result { - let mut data_hash = ::sha1::Sha1::new(); - data_hash.update(&data); - let data_hash = data_hash.digest().bytes().to_vec(); - - let id = audio::table - .select(audio::id) - .filter(audio::data_hash.eq(&data_hash)) - .get_results::(conn)?; - - if let Some(id) = id.first() { - return Ok(*id); - } - - let metadata = Metadata::create(conn, by_user)?; - - let new_audio = NewAudio { - data, - data_hash, - metadata_id: metadata.id, - }; - - ::diesel::insert_into(audio::table) - .values(&new_audio) - .returning(audio::id) - .get_result(conn) - .map_err(Error::from) - } -} - -#[derive(Insertable, PartialEq, Debug)] -#[table_name="audio"] -pub struct NewAudio { - pub data: Vec, - pub metadata_id: i32, - pub data_hash: Vec, -} - - -#[derive(Queryable, Identifiable, PartialEq, Debug)] -#[table_name="images"] -pub struct Image { - pub id: i32, - pub data: Vec, - pub metadata_id: i32, - pub data_hash: Vec, - pub filename: String, -} - -impl Image { - pub fn create(conn: &PgConnection, filename: &str, data: Vec, by_user: u64) -> Result { - let mut data_hash = ::sha1::Sha1::new(); - data_hash.update(&data); - let data_hash = data_hash.digest().bytes().to_vec(); - - let id = images::table - .select(images::id) - .filter(images::data_hash.eq(&data_hash)) - .get_results::(conn)?; - - if let Some(id) = id.first() { - return Ok(*id); - } - - let metadata = Metadata::create(conn, by_user)?; - - let new_image = NewImage { - data, - data_hash, - filename: filename.to_owned(), - metadata_id: metadata.id, - }; - - ::diesel::insert_into(images::table) - .values(&new_image) - .returning(images::id) - .get_result(conn) - .map_err(Error::from) - } -} - -#[derive(Insertable, PartialEq, Debug)] -#[table_name="images"] -pub struct NewImage { - pub data: Vec, - pub metadata_id: i32, - pub data_hash: Vec, - pub filename: String, -} - - -#[derive(Queryable, Identifiable, PartialEq, Debug)] -#[table_name="metadata"] -pub struct Metadata { - pub id: i32, - pub created: NaiveDateTime, - pub created_by: i64, -} - -impl Metadata { - pub fn create(conn: &PgConnection, by_user: u64) -> Result { - ::diesel::insert_into(metadata::table) - .values(&NewMetadata { - created_by: by_user as i64, - }) - .get_result::(conn) - .map_err(Error::from) - } - - pub fn find(conn: &PgConnection, id: i32) -> Result { - metadata::table.find(id) - .get_result::(conn) - .map_err(Error::from) - } -} - -#[derive(Insertable, PartialEq, Debug)] -#[table_name="metadata"] -pub struct NewMetadata { - pub created_by: i64, -} - - -#[derive(Queryable, Identifiable, PartialEq, Debug)] -#[table_name="audit_records"] -pub struct AuditRecord { - pub id: i32, - pub updated: NaiveDateTime, - pub updated_by: i64, - pub metadata_id: i32, -} - -impl AuditRecord { - pub fn create(conn: &PgConnection, metadata: i32, by_user: u64) -> Result { - ::diesel::insert_into(audit_records::table) - .values(&NewAuditRecord { - updated_by: by_user as i64, - metadata_id: metadata, - }) - .get_result::(conn) - .map_err(Error::from) - } -} - -#[derive(Insertable, PartialEq, Debug)] -#[table_name="audit_records"] -pub struct NewAuditRecord { - pub updated_by: i64, - pub metadata_id: i32, -} - -#[derive(Queryable, Identifiable, PartialEq, Debug)] -#[table_name="tombstones"] -pub struct Tombstone { - pub id: i32, - pub deleted: NaiveDateTime, - pub deleted_by: i64, - pub metadata_id: i32, - pub meme_id: i32, -} - - -#[derive(Insertable, PartialEq, Debug)] -#[table_name="tombstones"] -pub struct NewTombstone { - pub deleted_by: i64, - pub metadata_id: i32, - pub meme_id: i32, -} +use chrono::naive::NaiveDateTime; +use diesel::prelude::*; + +use crate::{ + db::schema::*, + Error, + Result, +}; + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="memes"] +pub struct Meme { + pub id: i32, + pub title: String, + pub content: Option, + pub image_id: Option, + pub audio_id: Option, + pub metadata_id: i32, +} + +impl Meme { + pub fn image(&self, conn: &PgConnection) -> Option> { + self.image_id.map(|x: i32| images::table.filter(images::id.eq(x)).first(conn).map_err(Error::from)) + } + + pub fn audio(&self, conn: &PgConnection) -> Option> { + self.audio_id.map(|x: i32| audio::table.filter(audio::id.eq(x)).first(conn).map_err(Error::from)) + } + + pub fn find(conn: &PgConnection, id: i32) -> Result { + memes::table.find(id).get_result(conn).map_err(Error::from) + } +} + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="memes"] +pub struct NewMeme { + pub title: String, + pub content: Option, + pub image_id: Option, + pub audio_id: Option, + pub metadata_id: i32, +} + +impl NewMeme { + pub fn save(mut self, conn: &PgConnection, by_user: u64) -> Result { + let metadata = Metadata::create(conn, by_user)?; + + self.metadata_id = metadata.id; + + ::diesel::insert_into(memes::table) + .values(&self) + .get_result::(conn) + .map_err(Error::from) + } +} + + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="audio"] +pub struct Audio { + pub id: i32, + pub data: Vec, + pub metadata_id: i32, + pub data_hash: Vec, +} + +impl Audio { + pub fn create(conn: &PgConnection, data: Vec, by_user: u64) -> Result { + let mut data_hash = ::sha1::Sha1::new(); + data_hash.update(&data); + let data_hash = data_hash.digest().bytes().to_vec(); + + let id = audio::table + .select(audio::id) + .filter(audio::data_hash.eq(&data_hash)) + .get_results::(conn)?; + + if let Some(id) = id.first() { + return Ok(*id); + } + + let metadata = Metadata::create(conn, by_user)?; + + let new_audio = NewAudio { + data, + data_hash, + metadata_id: metadata.id, + }; + + ::diesel::insert_into(audio::table) + .values(&new_audio) + .returning(audio::id) + .get_result(conn) + .map_err(Error::from) + } +} + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="audio"] +pub struct NewAudio { + pub data: Vec, + pub metadata_id: i32, + pub data_hash: Vec, +} + + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="images"] +pub struct Image { + pub id: i32, + pub data: Vec, + pub metadata_id: i32, + pub data_hash: Vec, + pub filename: String, +} + +impl Image { + pub fn create(conn: &PgConnection, filename: &str, data: Vec, by_user: u64) -> Result { + let mut data_hash = ::sha1::Sha1::new(); + data_hash.update(&data); + let data_hash = data_hash.digest().bytes().to_vec(); + + let id = images::table + .select(images::id) + .filter(images::data_hash.eq(&data_hash)) + .get_results::(conn)?; + + if let Some(id) = id.first() { + return Ok(*id); + } + + let metadata = Metadata::create(conn, by_user)?; + + let new_image = NewImage { + data, + data_hash, + filename: filename.to_owned(), + metadata_id: metadata.id, + }; + + ::diesel::insert_into(images::table) + .values(&new_image) + .returning(images::id) + .get_result(conn) + .map_err(Error::from) + } +} + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="images"] +pub struct NewImage { + pub data: Vec, + pub metadata_id: i32, + pub data_hash: Vec, + pub filename: String, +} + + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="metadata"] +pub struct Metadata { + pub id: i32, + pub created: NaiveDateTime, + pub created_by: i64, +} + +impl Metadata { + pub fn create(conn: &PgConnection, by_user: u64) -> Result { + ::diesel::insert_into(metadata::table) + .values(&NewMetadata { + created_by: by_user as i64, + }) + .get_result::(conn) + .map_err(Error::from) + } + + pub fn find(conn: &PgConnection, id: i32) -> Result { + metadata::table.find(id) + .get_result::(conn) + .map_err(Error::from) + } +} + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="metadata"] +pub struct NewMetadata { + pub created_by: i64, +} + + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="audit_records"] +pub struct AuditRecord { + pub id: i32, + pub updated: NaiveDateTime, + pub updated_by: i64, + pub metadata_id: i32, +} + +impl AuditRecord { + pub fn create(conn: &PgConnection, metadata: i32, by_user: u64) -> Result { + ::diesel::insert_into(audit_records::table) + .values(&NewAuditRecord { + updated_by: by_user as i64, + metadata_id: metadata, + }) + .get_result::(conn) + .map_err(Error::from) + } +} + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="audit_records"] +pub struct NewAuditRecord { + pub updated_by: i64, + pub metadata_id: i32, +} + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="tombstones"] +pub struct Tombstone { + pub id: i32, + pub deleted: NaiveDateTime, + pub deleted_by: i64, + pub metadata_id: i32, + pub meme_id: i32, +} + + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="tombstones"] +pub struct NewTombstone { + pub deleted_by: i64, + pub metadata_id: i32, + pub meme_id: i32, +} diff --git a/src/main.rs b/src/main.rs index de2d3c5..d380308 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ #![feature(transpose_result)] -#![feature(crate_in_paths)] extern crate chrono; #[cfg(feature = "diesel")] @@ -19,7 +18,6 @@ extern crate typemap; extern crate url; use std::{ - env, thread, time::{ Duration, @@ -27,7 +25,6 @@ use std::{ }, }; - use dotenv::dotenv; use failure::Error; use serenity::{ @@ -79,7 +76,7 @@ impl EventHandler for Handler { } fn run() -> Result<()> { - let token = &dotenv::var("THULANI_TOKEN").map_err(|e| format_err!("missing token"))?; + let token = &dotenv::var("THULANI_TOKEN").map_err(|_| format_err!("missing token"))?; let mut client = Client::new(token, Handler)?; commands::VoiceManager::register(&mut client); @@ -97,7 +94,7 @@ fn run() -> Result<()> { .case_insensitivity(true) ) .before(|_ctx, message, cmd| { - let result = message.guild_id().map_or(false, |x| x.0 == *TARGET_GUILD); + let result = message.guild_id.map_or(false, |x| x.0 == *TARGET_GUILD); debug!("got command '{}' from user '{}' ({}). accept: {}", cmd, message.author.name, message.author.id, result); result -- cgit v1.3.1