Merge pull request #7 from Trivernis/develop

Subcrate coreutils
pull/16/head v0.2.2
Trivernis 3 years ago committed by GitHub
commit 6d9415ba9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -35,5 +35,11 @@ jobs:
- name: Build
run: cargo build --verbose
- name: Run tests
- name: Test coreutils
run: cargo test --verbose --package bot-coreutils
- name: Test database
run: cargo test --verbose --package bot-database
- name: Test binary
run: cargo test --verbose

44
Cargo.lock generated

@ -178,6 +178,31 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bot-coreutils"
version = "0.1.0"
dependencies = [
"log 0.4.14",
"mime_guess",
"rand 0.8.3",
"tokio",
"url",
]
[[package]]
name = "bot-database"
version = "0.1.0"
dependencies = [
"chrono",
"diesel",
"diesel_migrations",
"dotenv",
"log 0.4.14",
"r2d2",
"thiserror",
"tokio-diesel",
]
[[package]]
name = "bumpalo"
version = "3.6.1"
@ -377,20 +402,6 @@ dependencies = [
"num_cpus",
]
[[package]]
name = "database"
version = "0.1.0"
dependencies = [
"chrono",
"diesel",
"diesel_migrations",
"dotenv",
"log 0.4.14",
"r2d2",
"thiserror",
"tokio-diesel",
]
[[package]]
name = "diesel"
version = "1.4.6"
@ -2269,13 +2280,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tobi-rs"
version = "0.2.1"
version = "0.2.2"
dependencies = [
"aspotify",
"bot-coreutils",
"bot-database",
"chrono",
"chrono-tz",
"colored",
"database",
"dotenv",
"fern",
"futures",

@ -1,12 +1,14 @@
[package]
name = "tobi-rs"
version = "0.2.1"
version = "0.2.2"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bot-database = {path="./bot-database"}
bot-coreutils = {path="./bot-coreutils"}
serenity = "0.10.5"
dotenv = "0.15.0"
tokio = { version = "1.4.0", features = ["macros", "rt-multi-thread"] }
@ -26,7 +28,6 @@ fern = "0.6.0"
chrono = "0.4.19"
colored = "2.0.0"
sysinfo = "0.16.5"
database = {path="./database"}
reqwest = "0.11.2"
chrono-tz = "0.5.3"
sauce-api = "0.7.1"

@ -0,0 +1 @@
target

@ -0,0 +1,314 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bot-coreutils"
version = "0.1.0"
dependencies = [
"log",
"mime_guess",
"rand",
"tokio",
"url",
]
[[package]]
name = "bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "idna"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "libc"
version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "mio"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "once_cell"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project-lite"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "rand"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
]
[[package]]
name = "rand_chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
dependencies = [
"libc",
]
[[package]]
name = "tinyvec"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
dependencies = [
"autocfg",
"bytes",
"libc",
"memchr",
"mio",
"once_cell",
"pin-project-lite",
"signal-hook-registry",
"winapi",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [
"matches",
]
[[package]]
name = "unicode-normalization"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
dependencies = [
"tinyvec",
]
[[package]]
name = "url"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

@ -0,0 +1,14 @@
[package]
name = "bot-coreutils"
version = "0.1.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tokio = {version = "1.4.0", features = ["process", "io-util"]}
log = "0.4.14"
url = "2.2.1"
mime_guess = "2.0.3"
rand = "0.8.3"

@ -0,0 +1,7 @@
#[cfg(test)]
mod tests;
pub mod process;
pub mod shuffle;
/// Utilities to quickly check strings that represent urls
pub mod url;

@ -0,0 +1,26 @@
use std::io;
use std::process::Stdio;
use tokio::io::AsyncReadExt;
use tokio::process::Command;
/// Asynchronously runs a given command and returns the output
pub async fn run_command_async(command: &str, args: &[&str]) -> io::Result<String> {
log::trace!("Running command '{}' with args {:?}", command, args);
let cmd = Command::new(command)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stderr = String::new();
let mut output = String::new();
cmd.stderr.unwrap().read_to_string(&mut stderr).await?;
if stderr.len() != 0 {
log::debug!("STDERR of command {}: {}", command, stderr);
}
cmd.stdout.unwrap().read_to_string(&mut output).await?;
log::trace!("Command output is {}", output);
Ok(output)
}

@ -0,0 +1,20 @@
use rand::Rng;
use std::collections::VecDeque;
pub trait Shuffle {
fn shuffle(&mut self);
}
impl<T> Shuffle for VecDeque<T> {
/// Fisher-Yates shuffle implementation
/// for VecDeque.
fn shuffle(&mut self) {
let mut rng = rand::thread_rng();
let mut i = self.len();
while i >= 2 {
i -= 1;
self.swap(i, rng.gen_range(0..i + 1))
}
}
}

@ -0,0 +1,2 @@
#[cfg(test)]
mod url_tests;

@ -0,0 +1,37 @@
use crate::url::*;
#[test]
fn it_returns_the_domain_name() {
assert_eq!(
get_domain_for_url("https://domain.com/sub/sub"),
Some("domain.com".to_string())
);
assert_eq!(
get_domain_for_url("other-domain.com"),
Some("other-domain.com".to_string())
);
assert_eq!(get_domain_for_url("Invalid URL"), None);
assert_eq!(get_domain_for_url("file:////what/a/file.txt"), None);
assert_eq!(
get_domain_for_url("https://www.domain.com/sub",),
Some("domain.com".to_string())
);
}
#[test]
fn it_checks_for_image() {
assert!(is_image("domain.com/image.png"));
assert!(is_image("https://domain.com/image.jpeg?yo=someparam"));
assert!(!is_image("https://domain.com"));
assert!(!is_image("https://domain.com/file.pdf"));
assert!(!is_image("not an url"));
}
#[test]
fn it_checks_for_video() {
assert!(is_video("domain.com/video.mp4"));
assert!(is_video("https://domain.com/video.webm?yo=someparam"));
assert!(!is_video("https://domain.com"));
assert!(!is_video("https://domain.com/file.pdf"));
assert!(!is_video("not an url"));
}

@ -0,0 +1,71 @@
use mime_guess::{mime, Mime};
pub use url::*;
static PROTOCOL_HTTP: &str = "http://";
static PROTOCOL_HTTPS: &str = "https://";
static PROTOCOL_FILE: &str = "file:////";
static PROTOCOL_DATA: &str = "data:";
static PROTOCOLS: &[&str] = &[PROTOCOL_HTTP, PROTOCOL_HTTPS, PROTOCOL_FILE, PROTOCOL_DATA];
/// Adds the protocol in front of the url if it is missing from the input
fn add_missing_protocol(url_str: &str) -> String {
for protocol in PROTOCOLS {
if url_str.starts_with(protocol) {
return url_str.to_string();
}
}
format!("{}{}", PROTOCOL_HTTPS, url_str)
}
/// Parses the given url into the url representation
/// Allows for fuzzier input than the original method. If no protocol is provided,
/// it assumes https.
#[inline]
pub fn parse_url(url_str: &str) -> Result<Url, url::ParseError> {
let url_str = add_missing_protocol(url_str);
Url::parse(&url_str)
}
/// Returns the domain for a given url string
/// Example
/// ```
/// use bot_coreutils::url::get_domain_for_url;
///
/// assert_eq!(get_domain_for_url("https://reddit.com/r/anime"), Some("reddit.com".to_string()));
/// assert_eq!(get_domain_for_url("reddit.com"), Some("reddit.com".to_string()));
/// assert_eq!(get_domain_for_url("invalid url"), None);
/// ```
pub fn get_domain_for_url(url_str: &str) -> Option<String> {
let url = parse_url(url_str).ok()?;
let domain = url.domain()?;
Some(domain.trim_start_matches("www.").to_string())
}
/// Guesses the mime for a given url string
#[inline]
fn guess_mime_for_url(url_str: &str) -> Option<Mime> {
parse_url(url_str)
.ok()
.and_then(|u| mime_guess::from_path(u.path()).first())
}
/// Returns if a given url could be an image
pub fn is_image(url_str: &str) -> bool {
if let Some(guess) = guess_mime_for_url(url_str) {
guess.type_() == mime::IMAGE
} else {
false
}
}
/// Returns if a given url could be a video
pub fn is_video(url_str: &str) -> bool {
if let Some(guess) = guess_mime_for_url(url_str) {
guess.type_() == mime::VIDEO
} else {
false
}
}

@ -1,5 +1,5 @@
[package]
name = "database"
name = "bot-database"
version = "0.1.0"
authors = ["trivernis <trivernis@protonmail.com>"]
edition = "2018"

@ -18,7 +18,7 @@ pub use database::Database;
type PoolConnection = Pool<ConnectionManager<PgConnection>>;
embed_migrations!("../database/migrations");
embed_migrations!("../bot-database/migrations");
fn get_connection() -> DatabaseResult<PoolConnection> {
dotenv::dotenv()?;

@ -1,8 +1,12 @@
use std::collections::{HashMap, HashSet};
use bot_database::get_database;
use serenity::client::Context;
use serenity::framework::standard::macros::hook;
use serenity::framework::standard::{CommandResult, DispatchError};
use serenity::framework::StandardFramework;
use serenity::model::channel::Message;
use serenity::model::id::UserId;
use serenity::Client;
use songbird::SerenityInit;
@ -12,9 +16,6 @@ use crate::utils::context_data::{
DatabaseContainer, EventDrivenMessageContainer, Store, StoreData,
};
use crate::utils::error::{BotError, BotResult};
use database::get_database;
use serenity::model::id::UserId;
use std::collections::{HashMap, HashSet};
pub async fn get_client() -> BotResult<Client> {
let token = dotenv::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?;

@ -1,8 +1,9 @@
use crate::providers::settings::{get_setting, Setting};
use crate::utils::error::BotResult;
use serenity::model::channel::Message;
use serenity::prelude::*;
use crate::providers::settings::{get_setting, Setting};
use crate::utils::error::BotResult;
/// Deletes a message automatically if configured that way
pub async fn handle_autodelete(ctx: &Context, msg: &Message) -> BotResult<()> {
if let Some(guild_id) = msg.guild_id {

@ -1,6 +1,5 @@
use std::collections::HashSet;
use crate::commands::common::handle_autodelete;
use serenity::client::Context;
use serenity::framework::standard::macros::help;
use serenity::framework::standard::{help_commands, Args};
@ -8,6 +7,8 @@ use serenity::framework::standard::{CommandGroup, CommandResult, HelpOptions};
use serenity::model::channel::Message;
use serenity::model::id::UserId;
use crate::commands::common::handle_autodelete;
#[help]
#[max_levenshtein_distance(2)]
pub async fn help(

@ -1,5 +1,6 @@
use serenity::framework::standard::macros::group;
use about::ABOUT_COMMAND;
use pekofy::PEKOFY_COMMAND;
use ping::PING_COMMAND;
use qalc::QALC_COMMAND;
@ -8,8 +9,8 @@ use shutdown::SHUTDOWN_COMMAND;
use stats::STATS_COMMAND;
use time::TIME_COMMAND;
use timezones::TIMEZONES_COMMAND;
use about::ABOUT_COMMAND;
mod about;
pub(crate) mod help;
mod pekofy;
mod ping;
@ -19,7 +20,6 @@ mod shutdown;
mod stats;
mod time;
mod timezones;
mod about;
#[group]
#[commands(ping, stats, shutdown, pekofy, time, timezones, qalc, sauce, about)]

@ -1,10 +1,11 @@
use crate::utils::get_previous_message_or_reply;
use rand::prelude::*;
use regex::Regex;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use serenity::{framework::standard::macros::command, prelude::*};
use crate::utils::get_previous_message_or_reply;
// return a normal peko in most cases
static PEKOS: &[&str] = &[
"peko",

@ -1,10 +1,11 @@
use crate::providers::qalc;
use regex::Regex;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::providers::qalc;
static QALC_HELP: &[&str] = &["help", "--help", "-h", "h"];
#[command]

@ -1,14 +1,15 @@
use crate::messages::sauce::show_sauce_menu;
use crate::utils::{get_previous_message_or_reply, is_image, is_video};
use sauce_api::Sauce;
use crate::utils::context_data::Store;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
use bot_coreutils::url;
use crate::messages::sauce::show_sauce_menu;
use crate::utils::context_data::Store;
use crate::utils::get_previous_message_or_reply;
#[command]
#[description("Searches for the source of a previously posted image or an image replied to.")]
#[usage("")]
@ -29,7 +30,7 @@ async fn sauce(ctx: &Context, msg: &Message) -> CommandResult {
.attachments
.into_iter()
.map(|a| a.url)
.filter(|url| is_image(url) || is_video(url))
.filter(|url| url::is_image(url) || url::is_video(url))
.collect();
log::debug!("Getting embedded images...");
@ -37,7 +38,7 @@ async fn sauce(ctx: &Context, msg: &Message) -> CommandResult {
.embeds
.into_iter()
.filter_map(|e| e.thumbnail.map(|t| t.url).or(e.image.map(|i| i.url)))
.filter(|url| is_image(url) || is_video(url))
.filter(|url| url::is_image(url) || url::is_video(url))
.collect::<Vec<String>>();
attachment_urls.append(&mut embed_images);

@ -1,9 +1,11 @@
use crate::commands::common::handle_autodelete;
use std::process;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use serenity::prelude::*;
use std::process;
use crate::commands::common::handle_autodelete;
#[command]
#[description("Shuts down the bot with the specified exit code")]

@ -1,13 +1,15 @@
use crate::commands::common::handle_autodelete;
use std::process;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use chrono::Duration as ChronoDuration;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
use serenity::prelude::*;
use std::process;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use sysinfo::{ProcessExt, SystemExt};
use crate::commands::common::handle_autodelete;
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
#[command]

@ -1,3 +1,5 @@
use std::mem;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
@ -6,7 +8,6 @@ use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::get_queue_for_guild;
use crate::messages::music::NowPlayingMessage;
use std::mem;
#[command]
#[only_in(guilds)]

@ -1,9 +1,8 @@
use std::mem;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::sync::Arc;
use std::time::Duration;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::youtube_dl;
use crate::utils::context_data::{DatabaseContainer, Store};
use crate::utils::error::{BotError, BotResult};
use regex::Regex;
use serenity::async_trait;
use serenity::client::Context;
@ -12,29 +11,12 @@ use serenity::http::Http;
use serenity::model::channel::Message;
use serenity::model::guild::Guild;
use serenity::model::id::{ChannelId, GuildId, UserId};
use serenity::model::user::User;
use songbird::{
Call, Event, EventContext, EventHandler as VoiceEventHandler, Songbird, TrackEvent,
};
use std::mem;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::time::Duration;
use tokio::sync::Mutex;
mod clear_queue;
mod current;
mod join;
mod leave;
mod lyrics;
mod pause;
mod play;
mod play_next;
mod playlists;
mod queue;
mod save_playlist;
mod shuffle;
mod skip;
use crate::providers::settings::{get_setting, Setting};
use clear_queue::CLEAR_QUEUE_COMMAND;
use current::CURRENT_COMMAND;
use join::JOIN_COMMAND;
@ -46,10 +28,29 @@ use play_next::PLAY_NEXT_COMMAND;
use playlists::PLAYLISTS_COMMAND;
use queue::QUEUE_COMMAND;
use save_playlist::SAVE_PLAYLIST_COMMAND;
use serenity::model::user::User;
use shuffle::SHUFFLE_COMMAND;
use skip::SKIP_COMMAND;
use crate::providers::music::queue::{MusicQueue, Song};
use crate::providers::music::youtube_dl;
use crate::providers::settings::{get_setting, Setting};
use crate::utils::context_data::{DatabaseContainer, Store};
use crate::utils::error::{BotError, BotResult};
mod clear_queue;
mod current;
mod join;
mod leave;
mod lyrics;
mod pause;
mod play;
mod play_next;
mod playlists;
mod queue;
mod save_playlist;
mod shuffle;
mod skip;
#[group]
#[commands(
join,

@ -3,12 +3,11 @@ use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandError, CommandResult};
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::commands::music::{
get_channel_for_author, get_queue_for_guild, get_songs_for_query, get_voice_manager,
join_channel, play_next_in_queue,
};
use crate::commands::common::handle_autodelete;
use crate::providers::settings::{get_setting, Setting};
#[command]

@ -1,10 +1,11 @@
use crate::commands::common::handle_autodelete;
use crate::utils::context_data::get_database_from_context;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message;
use crate::commands::common::handle_autodelete;
use crate::utils::context_data::get_database_from_context;
#[command]
#[only_in(guilds)]
#[description("Displays a list of all saved playlists")]

@ -1,10 +1,11 @@
use crate::commands::music::is_dj;
use crate::utils::context_data::get_database_from_context;
use serenity::client::Context;
use serenity::framework::standard::macros::command;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use crate::commands::music::is_dj;
use crate::utils::context_data::get_database_from_context;
#[command]
#[only_in(guilds)]
#[description("Adds a playlist to the guilds saved playlists")]

@ -1,5 +1,3 @@
use crate::commands::music::get_queue_for_guild;
use crate::utils::context_data::EventDrivenMessageContainer;
use serenity::async_trait;
use serenity::client::Context;
use serenity::model::channel::Reaction;
@ -10,6 +8,9 @@ use serenity::model::id::{ChannelId, GuildId, MessageId};
use serenity::model::voice::VoiceState;
use serenity::prelude::*;
use crate::commands::music::get_queue_for_guild;
use crate::utils::context_data::EventDrivenMessageContainer;
pub(crate) struct Handler;
macro_rules! log_msg_fire_error {

@ -1,10 +1,12 @@
use crate::utils::error::BotResult;
use crate::utils::messages::ShareableMessage;
use std::sync::Arc;
use serenity::builder::CreateEmbed;
use serenity::http::Http;
use serenity::model::prelude::ChannelId;
use songbird::input::Metadata;
use std::sync::Arc;
use crate::utils::error::BotResult;
use crate::utils::messages::ShareableMessage;
#[derive(Clone)]
pub struct NowPlayingMessage {

@ -1,10 +1,13 @@
use crate::utils::error::BotResult;
use crate::utils::get_domain_for_url;
use std::cmp::Ordering;
use sauce_api::{SauceItem, SauceResult};
use serenity::builder::CreateMessage;
use serenity::{model::channel::Message, prelude::*};
use serenity_utils::prelude::*;
use std::cmp::Ordering;
use bot_coreutils::url::get_domain_for_url;
use crate::utils::error::BotResult;
static MAX_RESULTS: usize = 6;
static MIN_SIMILARITY: f32 = 50.0;

@ -1,7 +1,8 @@
use crate::utils::error::BotResult;
use regex::Regex;
use serde_derive::Deserialize;
use crate::utils::error::BotResult;
const API_ENDPOINT: &str = "https://api.lyrics.ovh/v1/";
/// Returns the lyrics of a song

@ -1,12 +1,13 @@
use std::collections::VecDeque;
use aspotify::{Track, TrackSimplified};
use songbird::tracks::TrackHandle;
use bot_coreutils::shuffle::Shuffle;
use crate::messages::music::NowPlayingMessage;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::providers::music::youtube_dl;
use crate::utils::shuffle_vec_deque;
use aspotify::{Track, TrackSimplified};
#[derive(Clone)]
pub struct MusicQueue {
@ -40,7 +41,7 @@ impl MusicQueue {
/// Shuffles the queue
pub fn shuffle(&mut self) {
shuffle_vec_deque(&mut self.inner)
self.inner.shuffle()
}
/// Returns a reference to the inner deque

@ -1,6 +1,7 @@
use aspotify::{Client, ClientCredentials, PlaylistItem, PlaylistItemType};
use crate::providers::music::queue::Song;
use crate::utils::error::{BotError, BotResult};
use aspotify::{Client, ClientCredentials, PlaylistItem, PlaylistItemType};
pub struct SpotifyApi {
client: Client,

@ -1,13 +1,16 @@
use crate::providers::music::queue::Song;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::utils::error::BotResult;
use crate::utils::process::run_command_async;
use futures::future::BoxFuture;
use futures::FutureExt;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::time::Duration;
use futures::future::BoxFuture;
use futures::FutureExt;
use bot_coreutils::process::run_command_async;
use crate::providers::music::queue::Song;
use crate::providers::music::responses::{PlaylistEntry, VideoInformation};
use crate::utils::error::BotResult;
static THREAD_LIMIT: u8 = 64;
/// Returns a list of youtube videos for a given url

@ -1,9 +1,13 @@
use bot_coreutils::process::run_command_async;
use crate::utils::error::BotResult;
use crate::utils::process::{run_command_async, sanitize_argument};
/// Runs the qalc command with the given expression
pub async fn qalc(expression: &str) -> BotResult<String> {
let expression = sanitize_argument(expression, true)?;
let result = run_command_async("qalc", &["-m", "1000", &*expression]).await?;
let result = run_command_async(
"qalc",
&["-m", "1000", format!("\"{}\"", &*expression).as_str()],
)
.await?;
Ok(result)
}

@ -1,8 +1,10 @@
use crate::utils::context_data::DatabaseContainer;
use crate::utils::error::{BotError, BotResult};
use std::str::FromStr;
use serenity::client::Context;
use serenity::model::prelude::GuildId;
use std::str::FromStr;
use crate::utils::context_data::DatabaseContainer;
use crate::utils::error::{BotError, BotResult};
pub static ALL_SETTINGS: &[Setting] = &[
Setting::MusicAutoShuffle,

@ -1,6 +1,10 @@
use std::collections::HashMap;
use std::env;
use std::sync::Arc;
use bot_database::Database;
use sauce_api::prelude::SauceNao;
use serenity::client::Context;
use serenity::model::id::GuildId;
use serenity::prelude::TypeMapKey;
use tokio::sync::Mutex;
@ -8,10 +12,6 @@ use tokio::sync::Mutex;
use crate::providers::music::queue::MusicQueue;
use crate::providers::music::spotify::SpotifyApi;
use crate::utils::messages::EventDrivenMessage;
use database::Database;
use sauce_api::prelude::SauceNao;
use serenity::client::Context;
use std::env;
pub struct Store;

@ -8,7 +8,7 @@ pub enum BotError {
SerenityError(#[from] serenity::Error),
#[error("Database Error: {0}")]
Database(#[from] database::error::DatabaseError),
Database(#[from] bot_database::error::DatabaseError),
#[error("Missing Bot Token")]
MissingToken,

@ -4,13 +4,14 @@
* See LICENSE for more information
*/
use chrono::Local;
use colored::*;
use log::{Level, LevelFilter};
use std::fs;
use std::path::PathBuf;
use std::str::FromStr;
use chrono::Local;
use colored::*;
use log::{Level, LevelFilter};
/// Initializes the env_logger with a custom format
/// that also logs the thread names
pub fn init_logger() {

@ -1,10 +1,12 @@
use crate::utils::error::BotResult;
use std::sync::Arc;
use serenity::async_trait;
use serenity::builder::{CreateMessage, EditMessage};
use serenity::http::{CacheHttp, Http};
use serenity::model::channel::{Message, Reaction};
use serenity::model::id::{ChannelId, MessageId};
use std::sync::Arc;
use crate::utils::error::BotResult;
#[async_trait]
pub trait EventDrivenMessage: Send + Sync {

@ -1,25 +1,12 @@
use crate::utils::error::BotResult;
use rand::Rng;
use regex::Regex;
use serenity::client::Context;
use serenity::model::channel::Message;
use std::collections::VecDeque;
use crate::utils::error::BotResult;
pub(crate) mod context_data;
pub(crate) mod error;
pub(crate) mod logging;
pub(crate) mod messages;
pub(crate) mod process;
/// Fisher-Yates shuffle for VecDeque
pub fn shuffle_vec_deque<T>(deque: &mut VecDeque<T>) {
let mut rng = rand::thread_rng();
let mut i = deque.len();
while i >= 2 {
i -= 1;
deque.swap(i, rng.gen_range(0..i + 1))
}
}
/// Returns the message the given message is a reply to or the message sent before that
pub async fn get_previous_message_or_reply(
@ -38,49 +25,3 @@ pub async fn get_previous_message_or_reply(
Ok(referenced)
}
/// Returns the domain for a given url
pub fn get_domain_for_url(url: &str) -> Option<String> {
lazy_static::lazy_static! {
static ref DOMAIN_REGEX: Regex = Regex::new(r"^(https?://)?(www\.)?((\w+\.)+\w+).*$").unwrap();
}
let captures = DOMAIN_REGEX.captures(url)?;
captures.get(3).map(|c| c.as_str().to_string())
}
/// Returns the file for a given domain
pub fn get_file_name_for_domain(url: &str) -> Option<String> {
lazy_static::lazy_static! {
static ref FILE_REGEX: Regex = Regex::new(r"^(https?://)?(www\.)?(\w+\.)+\w+([^/]*/)*([^/]+)$").unwrap();
}
let captures = FILE_REGEX.captures(url)?;
captures.get(3).map(|c| c.as_str().to_string())
}
/// Returns if the given file is an image
#[inline]
pub fn is_image(url: &str) -> bool {
static IMAGE_EXTENSIONS: &[&str] = &["png", "webp", "jpg", "jpeg", "ico", "gif"];
for ext in IMAGE_EXTENSIONS {
if url.ends_with(ext) {
return true;
}
}
return false;
}
/// Returns if the given file is an image
#[inline]
pub fn is_video(url: &str) -> bool {
static IMAGE_EXTENSIONS: &[&str] = &["mp4", "flv", "mkv", "webm"];
for ext in IMAGE_EXTENSIONS {
if url.ends_with(ext) {
return true;
}
}
return false;
}

@ -1,51 +0,0 @@
use crate::utils::error::{BotError, BotResult};
use regex::Regex;
use std::io;
use std::process::Stdio;
use tokio::io::AsyncReadExt;
use tokio::process::Command;
/// Asynchronously runs a given command and returns the output
pub async fn run_command_async(command: &str, args: &[&str]) -> io::Result<String> {
log::trace!("Running command '{}' with args {:?}", command, args);
let cmd = Command::new(command)
.args(args)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let mut stderr = String::new();
let mut output = String::new();
cmd.stderr.unwrap().read_to_string(&mut stderr).await?;
if stderr.len() != 0 {
log::debug!("STDERR of command {}: {}", command, stderr);
}
cmd.stdout.unwrap().read_to_string(&mut output).await?;
log::trace!("Command output is {}", output);
Ok(output)
}
/// Sanitizes a command line argument and throws an error
/// on a possible injection attempt
pub fn sanitize_argument(arg: &str, detect_help: bool) -> BotResult<String> {
log::debug!("Sanitizing argument '{}'", arg);
lazy_static::lazy_static! {
static ref HELP_FLAG: Regex = Regex::new(r"^\s*(-*)h(elp)?\s*$").unwrap();
static ref FLAG_REGEX: Regex = Regex::new(r"^\s*-\w*\s*$").unwrap();
}
if FLAG_REGEX.is_match(arg) {
log::debug!("Detected STDIN injection");
return Err(BotError::CliInject);
}
if detect_help && HELP_FLAG.is_match(arg) {
log::debug!("Detected help injection");
return Err(BotError::CliInject);
}
let arg = arg.replace("--", "\\-\\-");
if arg.is_empty() {
return Err(BotError::CliInject);
}
log::debug!("Sanitized argument is '{}'", arg);
Ok(arg)
}
Loading…
Cancel
Save