Update deps and error handling

This commit is contained in:
hodasemi 2022-05-04 18:33:34 +02:00
parent 767ced2df0
commit 17d4a3e02a
7 changed files with 361 additions and 60 deletions

View file

@ -7,10 +7,9 @@ authors = ["hodasemi <michaelh.95@t-online.de>"]
[dependencies]
typemap = "~0.3"
serde_json = "*"
utilities = { git = "http://gavania.de/hodasemi/context" }
rusqlite = { version = "*", features = ["bundled"] }
serenity = { version = "0.8", default-features = false, features = [ "builder", "cache", "client", "framework", "gateway", "model", "standard_framework", "utils", "voice", "rustls_backend"]}
parking_lot = "*"
failure = "*"
anyhow = "*"
hey_listen = "*"
white_rabbit = "*"
rand = "*"

304
src/config_handler.rs Normal file
View file

@ -0,0 +1,304 @@
#![allow(unused)]
//! Config file handler
//! Cares about formatting and type conversion
use anyhow::{Context, Result};
use std::{
collections::HashMap,
fmt::Display,
fs::File,
io::{BufRead, BufReader, Write},
path::Path,
str::FromStr,
};
/// Value abstraction to convert to and from values
#[derive(Clone, Debug)]
pub enum Value {
Value(String),
Array(Vec<String>),
}
struct ConfigSection {
header: String,
body: HashMap<String, Value>,
}
impl Value {
/// Creates an empty value
pub fn empty() -> Value {
Value::Value("".to_string())
}
/// Creates an empty array value
pub fn empty_array() -> Value {
Value::Array(Vec::new())
}
/// Create a value `Value::Array(Vec<String>)`, internal conversion to string
///
/// # Arguments
///
/// `array` array of type, type has to implement `Display` trait
#[deprecated]
pub fn from_array<T: Display>(array: &[T]) -> Self {
Value::Array(array.iter().map(|v| format!("{}", v)).collect())
}
/// Creates a value `Value::Value(String)`, internal conversion to string
///
/// # Arguments
///
/// `value` type has to implement `Display` trait
#[deprecated]
pub fn from_value<T: Display>(value: &T) -> Self {
Value::Value(format!("{}", value))
}
pub fn to_array<T: FromStr>(&self) -> Result<Vec<T>> {
match self {
Value::Array(value_array) => {
let mut target_array = Vec::with_capacity(value_array.len());
for value_string in value_array {
match value_string.parse::<T>() {
Ok(val) => target_array.push(val),
Err(_) => {
return Err(anyhow::Error::msg(format!(
"ConfigHandler: Error while parsing Value Array: {}",
value_string
)));
}
}
}
Ok(target_array)
}
_ => Err(anyhow::Error::msg(
"ConfigHandler: Error when requesting the wrong value type",
)),
}
}
pub fn to_value<T: FromStr>(&self) -> Result<T> {
match self {
Value::Value(value_string) => match value_string.parse::<T>() {
Ok(val) => Ok(val),
Err(_) => Err(anyhow::Error::msg(format!(
"ConfigHandler: Error while parsing Value Array: {}",
value_string
))),
},
_ => Err(anyhow::Error::msg(
"ConfigHandler: Error when requesting the wrong value type",
)),
}
}
}
impl<'a, T: Display> From<&'a T> for Value {
fn from(v: &'a T) -> Self {
Value::Value(format!("{}", v))
}
}
impl<'a, T: Display> From<&'a [T]> for Value {
fn from(v: &'a [T]) -> Self {
Value::Array(v.iter().map(|v| format!("{}", v)).collect())
}
}
/// Handler struct
pub struct ConfigHandler {}
impl ConfigHandler {
/// Reads the given config file
///
/// # Arguments
///
/// `file_name` file that is going to be read
pub fn read_config(
file_name: impl AsRef<Path>,
) -> Result<HashMap<String, HashMap<String, Value>>> {
let file = File::open(&file_name).with_context({
let file_name = file_name.as_ref().to_str().unwrap().to_string();
|| file_name
})?;
let mut infos = HashMap::new();
let mut current_section: Option<ConfigSection> = None;
for line_res in BufReader::new(file).lines() {
if let Ok(line) = line_res {
let mut trimmed = line.trim().to_string();
if trimmed.starts_with('#') || trimmed.is_empty() {
continue;
} else if trimmed.starts_with('[') && trimmed.ends_with(']') {
trimmed.remove(0);
trimmed.pop();
if let Some(ref section) = current_section {
infos.insert(section.header.clone(), section.body.clone());
}
current_section = Some(ConfigSection {
header: trimmed,
body: HashMap::new(),
});
} else {
let mut split = trimmed.split('=');
let key = match split.nth(0) {
Some(key) => key.trim().to_string(),
None => {
println!("cannot get key from line: {}", trimmed);
continue;
}
};
let value = match split.last() {
Some(value) => value.trim().to_string(),
None => {
println!("cannot get value from line: {}", trimmed);
continue;
}
};
if value.starts_with('[') && value.ends_with(']') {
let mut trimmed_value = value;
trimmed_value.remove(0);
trimmed_value.pop();
let value_split = trimmed_value.split(',');
let mut value_array = Vec::new();
for v in value_split {
let trimmed = v.trim();
if !trimmed.is_empty() {
value_array.push(trimmed.to_string());
}
}
if let Some(ref mut section) = current_section {
section.body.insert(key, Value::Array(value_array));
}
} else if let Some(ref mut section) = current_section {
section.body.insert(key, Value::Value(value));
}
}
}
}
// also push the last section
if let Some(section) = current_section {
infos.insert(section.header, section.body);
}
Ok(infos)
}
/// writes a formatted config file
///
/// # Arguments
///
/// `file_name` the file to which the config gets written
/// `sections` the sections and keys that are going to be written
pub fn write_config(
file_name: impl AsRef<Path>,
sections: &[(&str, Vec<(&str, Value)>)],
) -> Result<()> {
let mut file = File::create(file_name)?;
for (header, body) in sections {
let fmt_header = format!("[{}]\n", header);
file.write_all(fmt_header.as_bytes())?;
for (key, value) in body.iter() {
let fmt_key_value = format!(
"{} = {}\n",
key,
match value {
Value::Value(val) => val.clone(),
Value::Array(array) => {
let mut array_value = "[".to_string();
for (i, val) in array.iter().enumerate() {
// if element is not the last one
if i != array.len() - 1 {
array_value = format!("{}{}, ", array_value, val);
} else {
array_value = format!("{}{}", array_value, val);
}
}
format!("{}]", array_value)
}
}
);
file.write_all(fmt_key_value.as_bytes())?;
}
file.write_all("\n".as_bytes())?;
}
Ok(())
}
}
#[macro_export]
macro_rules! create_settings_section {
($struct_name:ident, $section_key:expr, {$($var:ident: $var_type:ty,)* $([$array:ident: $array_type:ty],)*} $(,$($derive:ident,)*)?) => {
#[derive(Default, Debug, Clone, PartialEq $(, $($derive,)* )? )]
pub struct $struct_name {
$(
pub $var: $var_type,
)*
$(
pub $array: Vec<$array_type>,
)*
}
impl $struct_name {
const SECTION_KEY: &'static str = $section_key;
pub fn load(
parsed_config: &std::collections::HashMap<String, std::collections::HashMap<String, Value>>
) -> anyhow::Result<Self> {
let mut me = Self::default();
if let Some(section) = parsed_config.get(Self::SECTION_KEY) {
$(
if let Some(value) = section.get(stringify!($var)) {
me.$var = value.to_value()?;
}
)*
$(
if let Some(array) = section.get(stringify!($array)) {
me.$array = array.to_array()?;
}
)*
}
Ok(me)
}
pub fn store(&self) -> (&str, Vec<(&str, Value)>) {
(
Self::SECTION_KEY,
vec![
$(
(stringify!($var), Value::from(&self.$var)),
)*
$(
(stringify!($array), Value::from(self.$array.as_slice())),
)*
]
)
}
}
};
}
#[macro_export]
macro_rules! create_settings_container {
($struct_name:ident, {$($var:ident: $var_type:ty$(,)?)*} $(,$($derive:ident,)*)? ) => {
#[derive(Default, Debug, Clone, PartialEq $(, $($derive,)* )? )]
pub struct $struct_name {
pub file_name: assetpath::AssetPath,
$(
pub $var: $var_type,
)*
}
impl $struct_name {
pub fn load(file: impl Into<assetpath::AssetPath>) -> anyhow::Result<Self> {
let file = file.into();
let parsed_stats_settings = ConfigHandler::read_config(&file.full_path())?;
Ok($struct_name {
file_name: file,
$(
$var: <$var_type>::load(&parsed_stats_settings)?,
)*
})
}
pub fn load_with_default(&mut self, file: impl Into<assetpath::AssetPath>) -> anyhow::Result<()> {
let file = file.into();
let parsed_stats_settings = ConfigHandler::read_config(&file.full_path())?;
self.file_name = file;
$(
self.$var = <$var_type>::load(&parsed_stats_settings)?;
)*
Ok(())
}
pub fn store(&self) -> anyhow::Result<()> {
ConfigHandler::write_config(
&self.file_name.full_path(),
&[
$(
self.$var.store(),
)*
]
)
}
}
};
}

View file

@ -6,6 +6,7 @@ extern crate parking_lot;
extern crate serde_json;
extern crate typemap;
mod config_handler;
mod player;
use serenity::{
@ -18,15 +19,15 @@ use serenity::{
};
// This imports `typemap`'s `Key` as `TypeMapKey`.
use anyhow::Result;
use serenity::prelude::*;
use std::sync::{Arc, Mutex};
use config_handler::{ConfigHandler, Value};
use player::prelude::*;
use std::collections::HashSet;
use utilities::prelude::*;
create_settings_section!(
Config,
"Meta",
@ -53,7 +54,7 @@ fn my_help(
help_commands::with_embeds(context, msg, args, &help_options, groups, owners)
}
fn main() -> VerboseResult<()> {
fn main() -> Result<()> {
// read config file
let config_file = ConfigHandler::read_config("bot.conf")?;

View file

@ -11,7 +11,7 @@ use serenity::{
model::channel::Message,
};
use utilities::prelude::*;
use anyhow::{anyhow, Result};
use std::sync::Once;
use std::thread;
@ -127,7 +127,7 @@ fn append_songs(
ctx: &Context,
msg: &Message,
mut source: Vec<Song>,
) -> VerboseResult<()> {
) -> Result<()> {
media.playlist_mut().append(&mut source);
println!("start playing");
@ -141,7 +141,7 @@ fn handle_http_request(
ctx: &serenity::client::Context,
msg: &serenity::model::channel::Message,
url: &str,
) -> VerboseResult<()> {
) -> Result<()> {
let mut names = Vec::new();
{
@ -149,18 +149,18 @@ fn handle_http_request(
let mut stmt = match sql.prepare("SELECT name FROM Vulva3 WHERE link = ?") {
Ok(statement) => statement,
Err(_) => create_error!("failed preparing data base access"),
Err(_) => return Err(anyhow!("failed preparing data base access")),
};
let rows = match stmt.query_map(&[url], |row| row.get(0) as rusqlite::Result<String>) {
Ok(rows) => rows,
Err(_) => create_error!("failed querying rows"),
Err(_) => return Err(anyhow!("failed querying rows")),
};
for name_result in rows {
let name = match name_result {
Ok(name) => name,
Err(_) => create_error!("failed getting name from row"),
Err(_) => return Err(anyhow!("failed getting name from row")),
};
names.push(name);
@ -170,7 +170,7 @@ fn handle_http_request(
if names.len() > 0 {
msg.channel_id
.say(&ctx.http, "song already loaded!")
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
append_songs(
media,
@ -186,7 +186,7 @@ fn handle_http_request(
msg.channel_id
.say(&ctx.http, format!("Error using youtube-dl: {}", why))
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
return Ok(());
}
@ -212,14 +212,14 @@ fn handle_http_request(
)
.is_err()
{
create_error!("failed inserting songs into db");
return Err(anyhow!("failed inserting songs into db"));
}
}
}
msg.channel_id
.say(&ctx.http, info)
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
append_songs(media, ctx, msg, source)?;
}
@ -231,7 +231,7 @@ fn handle_local_request(
media: &mut MediaData,
ctx: &serenity::client::Context,
msg: &serenity::model::channel::Message,
) -> VerboseResult<()> {
) -> Result<()> {
let mut songs = Vec::new();
{
@ -239,18 +239,18 @@ fn handle_local_request(
let mut stmt = match sql.prepare("SELECT name FROM Vulva3") {
Ok(statement) => statement,
Err(_) => create_error!("failed preparing data base access"),
Err(_) => return Err(anyhow!("failed preparing data base access")),
};
let rows = match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
Ok(rows) => rows,
Err(_) => create_error!("failed querying rows"),
Err(_) => return Err(anyhow!("failed querying rows")),
};
for name_result in rows {
let name = match name_result {
Ok(name) => name,
Err(_) => create_error!("failed getting name from row"),
Err(_) => return Err(anyhow!("failed getting name from row")),
};
songs.push(Song { name });
@ -270,7 +270,7 @@ fn handle_song_request(
ctx: &serenity::client::Context,
msg: &serenity::model::channel::Message,
pattern: &str,
) -> VerboseResult<()> {
) -> Result<()> {
println!("song request ({})", pattern);
let mut songs = Vec::new();
@ -283,18 +283,18 @@ fn handle_song_request(
pattern
)) {
Ok(statement) => statement,
Err(_) => create_error!("failed preparing data base access"),
Err(_) => return Err(anyhow!("failed preparing data base access")),
};
let rows = match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
Ok(rows) => rows,
Err(_) => create_error!("failed querying rows"),
Err(_) => return Err(anyhow!("failed querying rows")),
};
for name_result in rows {
let name = match name_result {
Ok(name) => name,
Err(_) => create_error!("failed getting name from row"),
Err(_) => return Err(anyhow!("failed getting name from row")),
};
songs.push(Song { name });
@ -313,7 +313,7 @@ fn handle_song_request(
} else {
msg.channel_id
.say(&ctx.http, format!("no song found with pattern {}", pattern))
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
}
Ok(())
@ -324,7 +324,7 @@ fn handle_tag_request(
ctx: &serenity::client::Context,
msg: &serenity::model::channel::Message,
pattern: &str,
) -> VerboseResult<()> {
) -> Result<()> {
let mut songs = Vec::new();
{
@ -333,37 +333,37 @@ fn handle_tag_request(
pattern
)) {
Ok(statement) => statement,
Err(_) => create_error!("failed preparing data base access"),
Err(_) => return Err(anyhow!("failed preparing data base access")),
};
let mut rows = match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>)
{
Ok(rows) => rows,
Err(_) => create_error!("failed querying rows"),
Err(_) => return Err(anyhow!("failed querying rows")),
};
if let None = rows.next() {
msg.channel_id
.say(&ctx.http, format!("tag ({}) not found", pattern))
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
return Ok(());
}
let mut stmt = match media.db().prepare(&format!("SELECT name FROM {}", pattern)) {
Ok(statement) => statement,
Err(_) => create_error!("failed preparing data base access"),
Err(_) => return Err(anyhow!("failed preparing data base access")),
};
let rows = match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
Ok(rows) => rows,
Err(_) => create_error!("failed querying rows"),
Err(_) => return Err(anyhow!("failed querying rows")),
};
for name_result in rows {
let name = match name_result {
Ok(name) => name,
Err(_) => create_error!("failed getting name from row"),
Err(_) => return Err(anyhow!("failed getting name from row")),
};
songs.push(Song { name });

View file

@ -13,10 +13,9 @@ use super::prelude::*;
use std::fs;
use anyhow::{anyhow, Result};
use rusqlite::{params, Connection};
use utilities::prelude::*;
#[derive(Debug)]
pub struct Song {
pub name: String,
@ -74,7 +73,7 @@ impl MediaData {
&mut self,
ctx: &serenity::client::Context,
msg: &serenity::model::channel::Message,
) -> Result<(), String> {
) -> Result<()> {
self.playlist.clear();
self.current_song = None;
self.song_name = String::new();
@ -124,11 +123,7 @@ impl MediaData {
&mut self.song_name
}
pub fn start_playing(
ctx: &Context,
mediadata: &mut MediaData,
msg: &Message,
) -> VerboseResult<()> {
pub fn start_playing(ctx: &Context, mediadata: &mut MediaData, msg: &Message) -> Result<()> {
// check if there is already playing
let already_started = mediadata.song().is_some();
@ -141,7 +136,7 @@ impl MediaData {
Ok(())
}
fn check_for_next(&mut self, ctx: &Context, msg: &Message) -> VerboseResult<String> {
fn check_for_next(&mut self, ctx: &Context, msg: &Message) -> Result<String> {
println!("check for next");
while !self.playlist().is_empty() {
@ -158,7 +153,7 @@ impl MediaData {
&first
),
)
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
let sql = self.db();
@ -166,19 +161,19 @@ impl MediaData {
let mut stmt =
match sql.prepare("SELECT name FROM sqlite_master WHERE type='table'") {
Ok(statement) => statement,
Err(_) => create_error!("failed preparing data base access"),
Err(_) => return Err(anyhow!("failed preparing data base access")),
};
let rows =
match stmt.query_map(params![], |row| row.get(0) as rusqlite::Result<String>) {
Ok(rows) => rows,
Err(_) => create_error!("failed querying rows"),
Err(_) => return Err(anyhow!("failed querying rows")),
};
for row in rows {
let table_name = match row {
Ok(name) => name,
Err(_) => create_error!("failed getting name from row"),
Err(_) => return Err(anyhow!("failed getting name from row")),
};
if let Err(_) = sql.execute(
@ -194,14 +189,14 @@ impl MediaData {
}
println!("no song found!");
create_error!("no suitable song found!")
Err(anyhow!("no suitable song found!"))
}
fn check_for_channel<T: RawMutex>(
manager: &mut MutexGuard<T, ClientVoiceManager>,
ctx: &Context,
msg: &Message,
) -> VerboseResult<()> {
) -> Result<()> {
println!("check for channel!");
let guild = guild(ctx, msg)?;
@ -216,7 +211,7 @@ impl MediaData {
.and_then(|voice_state| voice_state.channel_id)
{
Some(channel) => channel,
None => create_error!("author is not in a voice channel!"),
None => return Err(anyhow!("author is not in a voice channel!")),
};
println!("got author channel");
@ -238,7 +233,7 @@ impl MediaData {
Ok(())
}
pub fn next_song(ctx: &Context, mediadata: &mut MediaData, msg: &Message) -> VerboseResult<()> {
pub fn next_song(ctx: &Context, mediadata: &mut MediaData, msg: &Message) -> Result<()> {
println!("start next song");
let voice_manager = mediadata.voice_manager.clone();
let mut manager = voice_manager.lock();
@ -269,7 +264,7 @@ impl MediaData {
Some(handler) => handler,
None => {
println!("failed getting handler");
create_error!("error getting handler");
return Err(anyhow!("error getting handler"));
}
}
};
@ -278,7 +273,7 @@ impl MediaData {
let source = match ffmpeg(first.clone()) {
Ok(mpeg) => mpeg,
Err(_) => create_error!(format!("failed loading: {}", &first)),
Err(_) => return Err(anyhow!(format!("failed loading: {}", &first))),
};
handler.stop();
@ -295,7 +290,7 @@ impl MediaData {
msg.channel_id
.say(&ctx.http, format!("Playing song: {}", first))
.map_err(|err| format!("{}", err))?;
.map_err(|err| anyhow!("{}", err))?;
}
if need_to_leave {

View file

@ -8,14 +8,14 @@ use serenity::voice::Handler;
use std::sync::Arc;
use utilities::prelude::*;
use anyhow::{anyhow, Result};
// This imports `typemap`'s `Key` as `TypeMapKey`.
use serenity::prelude::*;
use super::prelude::*;
pub fn guild(ctx: &Context, msg: &Message) -> Result<Arc<SerRwLock<Guild>>, String> {
pub fn guild(ctx: &Context, msg: &Message) -> Result<Arc<SerRwLock<Guild>>> {
match msg.guild(&ctx.cache) {
Some(guild) => Ok(guild),
None => {
@ -25,12 +25,12 @@ pub fn guild(ctx: &Context, msg: &Message) -> Result<Arc<SerRwLock<Guild>>, Stri
.is_err()
{}
Err("failed getting Guild".to_string())
Err(anyhow!("failed getting Guild"))
}
}
}
pub fn guild_id(ctx: &Context, msg: &Message) -> Result<GuildId, String> {
pub fn guild_id(ctx: &Context, msg: &Message) -> Result<GuildId> {
let guild = guild(ctx, msg)?;
let guild_read = guild.read();
@ -47,7 +47,7 @@ pub fn handler<'a, T: RawMutex>(
pub fn channel_contains_author(
ctx: &mut serenity::client::Context,
msg: &serenity::model::channel::Message,
) -> VerboseResult<()> {
) -> Result<()> {
let guild = guild(ctx, msg)?;
let guild_id = guild.read().id;
@ -58,7 +58,7 @@ pub fn channel_contains_author(
.and_then(|voice_state| voice_state.channel_id)
{
Some(channel) => channel,
None => create_error!("author is not in a voice channel!"),
None => return Err(anyhow!("author is not in a voice channel!")),
};
if let Some(media) = ctx.data.read().get::<Media>() {
@ -69,7 +69,9 @@ pub fn channel_contains_author(
// check if the bot is in a channel
if let Some(bot_channel_id) = handler.channel_id {
if bot_channel_id != author_channel_id {
create_error!("author is not in the same voice channel as the bot!");
return Err(anyhow!(
"author is not in the same voice channel as the bot!"
));
}
}
};

View file

@ -23,7 +23,7 @@ fn convert_output(out: &Output) -> Result<Vec<String>, String> {
let file_name = line.split_off(FIRST_LOAD_PREFIX.len());
files.push(file_name.trim().to_string());
} else if line.ends_with(RELOAD_SUFFIX) {
line.split_off(line_len - RELOAD_SUFFIX.len());
line.truncate(line_len - RELOAD_SUFFIX.len());
let file_name = line.split_off(PREFIX.len());
files.push(file_name.trim().to_string());
}