Compare commits
10 Commits
4f9171d96a
...
3d712fdcb3
| Author | SHA1 | Date |
|---|---|---|
|
|
3d712fdcb3 | |
|
|
ec8447bc58 | |
|
|
0b2e2bcb4a | |
|
|
be130790ea | |
|
|
0354cc6569 | |
|
|
7609e2007e | |
|
|
cdf823a94d | |
|
|
88c5a1b1a0 | |
|
|
57b79e7a5e | |
|
|
c9f6f5ced0 |
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "manifold"
|
name = "manifold"
|
||||||
version = "2.0.0"
|
version = "4.0.0"
|
||||||
authors = ["Lucy Bladen <admin@lbladen.uk>"]
|
authors = ["Lucy Bladen <admin@lbladen.uk>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
|
@ -22,7 +22,7 @@ dirs = "5.0.1"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
num = "0.4.1"
|
num = "0.4.1"
|
||||||
poise = "0.5.5"
|
poise = { version = "0.5.*", features = [ "cache" ] }
|
||||||
r2d2 = "0.8.9"
|
r2d2 = "0.8.9"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
regex = "1.5.4"
|
regex = "1.5.4"
|
||||||
|
|
|
||||||
|
|
@ -4,32 +4,7 @@ use crate::error::ManifoldError;
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, owners_only)]
|
#[poise::command(slash_command, prefix_command, owners_only)]
|
||||||
async fn dump_config(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
async fn dump_config(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
let config = &ctx.data().bot_config;
|
ctx.say(format!("Config dump; {:?}", &ctx.data().bot_config)).await?;
|
||||||
|
|
||||||
ctx.say(format!("Config dump; {:?}", config.config)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, owners_only)]
|
|
||||||
async fn get_environment(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
|
||||||
let environment = ctx.data().bot_config.get_environment();
|
|
||||||
|
|
||||||
ctx.say(format!("Currently running under the {} environment", environment)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, owners_only)]
|
|
||||||
async fn get_config(ctx: ManifoldContext<'_>, #[description="Config key"] key: String) -> ManifoldResult<()> {
|
|
||||||
let config = &ctx.data().bot_config;
|
|
||||||
|
|
||||||
let value = match config.get_value(&key) {
|
|
||||||
Ok(v) => v.clone(),
|
|
||||||
Err(_) => "not found, sorry!".to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
ctx.say(format!("Value for key {} was {}", &key, &value)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +52,6 @@ async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 6] {
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 4] {
|
||||||
[dump_config(), get_environment(), get_config(), register_commands(), set_activity(), save_world()]
|
[dump_config(), register_commands(), set_activity(), save_world()]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
use crate::models::frogtip::{Croak, Tip};
|
||||||
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
use crate::{ManifoldContext, ManifoldData};
|
||||||
|
use crate::models::animalpics::{AnimalPic, AnimalPicWrapper};
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("ft"))]
|
||||||
|
async fn frog_tip(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
|
||||||
|
debug!("Processing tip");
|
||||||
|
|
||||||
|
if let Some(tank) = ctx.data().bot_config.services.get("frog_tips") {
|
||||||
|
let tip: &Tip = &tank.next_or_fill::<Tip, Croak>().await?;
|
||||||
|
|
||||||
|
debug!("Tip: {:?}", tip);
|
||||||
|
|
||||||
|
ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?;
|
||||||
|
} else {
|
||||||
|
error!("No such fuel tank frog_tips");
|
||||||
|
ctx.send(|f| f.content("No tips could be located. Try rubbing it.".to_string()).reply(true)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("dog"))]
|
||||||
|
async fn dog_pic(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
animal_pic(ctx, "dog_pics").await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("cat"))]
|
||||||
|
async fn cat_pic(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
animal_pic(ctx, "cat_pics").await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn animal_pic(ctx: ManifoldContext<'_>, tank_name: &str) -> ManifoldResult<()> {
|
||||||
|
if let Some(tank) = ctx.data().bot_config.services.get(tank_name) {
|
||||||
|
let pic: &AnimalPic = &tank.next_or_fill::<AnimalPic, AnimalPicWrapper>().await?;
|
||||||
|
|
||||||
|
ctx.send(|f| f.content(format!("{}", pic)).reply(true)).await?;
|
||||||
|
} else {
|
||||||
|
error!("No such fuel tank {}", tank_name);
|
||||||
|
ctx.send(|f| f.content("No frens here at the moment. Perhaps some bacon would help?".to_string()).reply(true)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 3] {
|
||||||
|
[frog_tip(), dog_pic(), cat_pic()]
|
||||||
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
use crate::models::fueltank::Tip;
|
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
|
||||||
use crate::{ManifoldContext, ManifoldData};
|
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, aliases("ft"))]
|
|
||||||
async fn frogtip(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
|
||||||
|
|
||||||
debug!("Processing tip");
|
|
||||||
|
|
||||||
let tank = ctx.data().frogtip_fueltank.lock().await;
|
|
||||||
|
|
||||||
let tip: &Tip = &tank.next_or_fill().await?;
|
|
||||||
|
|
||||||
debug!("Tip: {:?}", tip);
|
|
||||||
|
|
||||||
ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 1] {
|
|
||||||
[frogtip()]
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
use crate::{ManifoldContext, ManifoldData};
|
||||||
|
use crate::models::dadjoke::{DadJoke, DadJokeWrapper};
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("dad"))]
|
||||||
|
async fn dad_joke(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
|
||||||
|
debug!("Processing tip");
|
||||||
|
|
||||||
|
if let Some(tank) = ctx.data().bot_config.services.get("dad_jokes") {
|
||||||
|
let tip: &DadJoke = &tank.next_or_fill::<DadJoke, DadJokeWrapper>().await?;
|
||||||
|
|
||||||
|
debug!("Tip: {:?}", tip);
|
||||||
|
|
||||||
|
ctx.send(|f| f.content(format!("{}", tip)).reply(true)).await?;
|
||||||
|
} else {
|
||||||
|
error!("No such fuel tank dad_jokes");
|
||||||
|
ctx.send(|f| f.content("Can't find any dad jokes. Maybe your dad isn't funny.".to_string()).reply(true)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 1] {
|
||||||
|
[dad_joke()]
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
mod admin;
|
mod admin;
|
||||||
mod frog;
|
mod animal;
|
||||||
mod convert;
|
mod convert;
|
||||||
mod core;
|
mod core;
|
||||||
|
mod joke;
|
||||||
|
mod nasa;
|
||||||
mod weather;
|
mod weather;
|
||||||
|
|
||||||
use crate::ManifoldCommand;
|
use crate::ManifoldCommand;
|
||||||
|
|
@ -9,8 +11,10 @@ use crate::ManifoldCommand;
|
||||||
pub fn collect_commands(injected: Vec<ManifoldCommand>) -> Vec<ManifoldCommand> {
|
pub fn collect_commands(injected: Vec<ManifoldCommand>) -> Vec<ManifoldCommand> {
|
||||||
core::commands().into_iter()
|
core::commands().into_iter()
|
||||||
.chain(admin::commands())
|
.chain(admin::commands())
|
||||||
.chain(frog::commands())
|
.chain(animal::commands())
|
||||||
.chain(convert::commands())
|
.chain(convert::commands())
|
||||||
|
.chain(joke::commands())
|
||||||
|
.chain(nasa::commands())
|
||||||
.chain(weather::commands())
|
.chain(weather::commands())
|
||||||
.chain(injected)
|
.chain(injected)
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
use crate::{ManifoldContext, ManifoldData};
|
||||||
|
use crate::models::nasa::*;
|
||||||
|
|
||||||
|
#[poise::command(slash_command, prefix_command, aliases("apod"))]
|
||||||
|
async fn nasa_apod(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
|
||||||
|
debug!("Processing tip");
|
||||||
|
|
||||||
|
if let Some(tank) = ctx.data().bot_config.services.get("nasa_apod") {
|
||||||
|
let photo: &NasaAstroPhoto = &tank.next_or_fill::<NasaAstroPhoto, NasaAstroPhoto>().await?;
|
||||||
|
|
||||||
|
debug!("Tip: {:?}", photo);
|
||||||
|
|
||||||
|
ctx.send(|f| {
|
||||||
|
f.content(format!("{}", photo))
|
||||||
|
.reply(true)
|
||||||
|
.embed(|e| {
|
||||||
|
e.title(&photo.title);
|
||||||
|
e.description(&photo.explanation);
|
||||||
|
e.image(&photo.hdurl);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
} else {
|
||||||
|
error!("No such fuel tank nasa_apod");
|
||||||
|
ctx.send(|f| f.content("The NASA Astronomy Photo of the Day was missing. Try looking up instead.".to_string()).reply(true)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 1] {
|
||||||
|
[nasa_apod()]
|
||||||
|
}
|
||||||
|
|
@ -5,16 +5,16 @@ use poise::ReplyHandle;
|
||||||
use poise::serenity_prelude::CreateEmbed;
|
use poise::serenity_prelude::CreateEmbed;
|
||||||
use crate::{ManifoldCommand, ManifoldContext};
|
use crate::{ManifoldCommand, ManifoldContext};
|
||||||
|
|
||||||
use crate::models::weather::{Weather, WeatherForecastRequestResponse};
|
use crate::models::weather::WeatherForecastRequestResponse;
|
||||||
use crate::models::user::UserInfo;
|
use crate::models::user::UserInfo;
|
||||||
use crate::error::ManifoldResult;
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
use crate::helpers::paginate;
|
use crate::helpers::paginate;
|
||||||
|
|
||||||
#[poise::command(slash_command, prefix_command, aliases("w"))]
|
#[poise::command(slash_command, prefix_command, aliases("w"))]
|
||||||
async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option<String>) -> ManifoldResult<()> {
|
async fn weather(ctx: ManifoldContext<'_>, #[rest] #[description="Location to look up weather for"] location: Option<String>) -> ManifoldResult<()> {
|
||||||
let my_message = ctx.say("Retrieving weather, be patient").await?;
|
let my_message = ctx.say("Retrieving weather, be patient").await?;
|
||||||
|
|
||||||
let weather_forecast = _get_weather(ctx, &my_message, location, Some(2)).await?;
|
let weather_forecast = _get_weather(ctx, &my_message, location).await?;
|
||||||
|
|
||||||
let responses = &ctx.data().responses;
|
let responses = &ctx.data().responses;
|
||||||
|
|
||||||
|
|
@ -81,7 +81,7 @@ pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[descripti
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle<'_>, location: Option<String>, days: Option<i32>) -> ManifoldResult<WeatherForecastRequestResponse> {
|
pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle<'_>, location: Option<String>) -> ManifoldResult<WeatherForecastRequestResponse> {
|
||||||
|
|
||||||
let mut weather_location: String = String::default();
|
let mut weather_location: String = String::default();
|
||||||
|
|
||||||
|
|
@ -90,7 +90,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
||||||
|
|
||||||
let userinfo = &mut ctx.data().user_info.lock().await;
|
let userinfo = &mut ctx.data().user_info.lock().await;
|
||||||
|
|
||||||
let weather_client = Weather::new(config.get_value(&"WeatherApiKey".to_string()).unwrap().clone(), config.get_value(&"WeatherBaseUrl".to_string()).unwrap().clone());
|
let weather_client = config.services.get("weather").ok_or_else(|| ManifoldError::from("No weather client available"))?;
|
||||||
|
|
||||||
if let Some(loc) = location {
|
if let Some(loc) = location {
|
||||||
weather_location = loc;
|
weather_location = loc;
|
||||||
|
|
@ -118,7 +118,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
||||||
|
|
||||||
debug!("Making weather request!");
|
debug!("Making weather request!");
|
||||||
|
|
||||||
let weather_forecast: WeatherForecastRequestResponse = match weather_client.get_weather_forecast(weather_location, days).await {
|
let weather_forecast: WeatherForecastRequestResponse = match weather_client.get_weather_forecast(weather_location).await {
|
||||||
Ok(w) => w,
|
Ok(w) => w,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
ctx.say(format!("Something went wrong. Maybe there is no weather there. {:?}", e)).await?;
|
ctx.say(format!("Something went wrong. Maybe there is no weather there. {:?}", e)).await?;
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,32 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use poise::serenity_prelude::model::prelude::{ChannelId, MessageId, RoleId};
|
use poise::serenity_prelude::ChannelId;
|
||||||
use crate::ManifoldResult;
|
use crate::ManifoldResult;
|
||||||
|
use crate::models::fueltank::FuelTank;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct Channels {
|
||||||
|
pub log: ChannelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
pub struct ManifoldConfig {
|
pub struct ManifoldConfig {
|
||||||
pub environment: String,
|
pub prefix: String,
|
||||||
pub path: PathBuf,
|
pub channels: Channels,
|
||||||
pub config: Config,
|
pub nickname: String,
|
||||||
|
pub services: HashMap<String, FuelTank>,
|
||||||
|
pub responses_file_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ManifoldConfig {
|
impl ManifoldConfig {
|
||||||
pub fn load_config(config_file: &String, bot_environment: &String) -> ManifoldResult<Self> {
|
pub fn new(config_file: &String, bot_environment: &String) -> ManifoldResult<Self> {
|
||||||
|
|
||||||
let settings = Config::builder()
|
let settings = Config::builder()
|
||||||
.set_override("BotEnvironment", bot_environment.clone())?
|
.add_source(config::File::with_name(&*format!("config/{}.{}", bot_environment.to_lowercase(), config_file)))
|
||||||
.add_source(config::File::with_name(config_file))
|
.add_source(config::Environment::with_prefix("manifold"))
|
||||||
.add_source(config::Environment::with_prefix("MANIFOLD"))
|
|
||||||
.build()?;
|
.build()?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(settings.try_deserialize()?)
|
||||||
environment: bot_environment.clone(),
|
|
||||||
path: PathBuf::from(config_file),
|
|
||||||
config: settings
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_environment(&self) -> &String {
|
|
||||||
&self.environment
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_path(&self) -> &PathBuf {
|
|
||||||
&self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_environment_mut(&mut self) -> &mut String {
|
|
||||||
&mut self.environment
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_path_mut(&mut self) -> &mut PathBuf {
|
|
||||||
&mut self.path
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_value(&self, key: &String) -> ManifoldResult<String> {
|
|
||||||
Ok(self.config.get_string(&format!("{}{}", &self.environment, key))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_role(&self, role_name: &String) -> ManifoldResult<RoleId> {
|
|
||||||
let key = format!("{}Role", role_name);
|
|
||||||
|
|
||||||
Ok(RoleId(self.get_value(&key)?.parse::<u64>()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_channel(&self, channel_name: &String) -> ManifoldResult<ChannelId> {
|
|
||||||
let key = format!("{}Channel", channel_name);
|
|
||||||
|
|
||||||
Ok(ChannelId(self.get_value(&key)?.parse::<u64>()?))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_message(&self, message_id: &String) -> ManifoldResult<MessageId> {
|
|
||||||
let key = format!("{}Message", message_id);
|
|
||||||
|
|
||||||
Ok(MessageId(self.get_value(&key)?.parse::<u64>()?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
248
src/events.rs
248
src/events.rs
|
|
@ -1,35 +1,52 @@
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
|
|
||||||
use poise::{Event, FrameworkContext};
|
use poise::{async_trait, Event, FrameworkContext};
|
||||||
use poise::serenity_prelude::model::{
|
use poise::serenity_prelude::model::{
|
||||||
gateway::Ready,
|
gateway::Ready,
|
||||||
id::ChannelId,
|
|
||||||
};
|
};
|
||||||
use poise::serenity_prelude::{Context, Message};
|
use poise::serenity_prelude::{ChannelId, Colour, Context, GuildId, Member, Message, MessageId, Role, RoleId, Timestamp, User};
|
||||||
use crate::ManifoldData;
|
use crate::ManifoldData;
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
use crate::models::user::UserInfo;
|
use crate::models::user::UserInfo;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait EventHandler {
|
||||||
|
async fn listen(ctx: &Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<()>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Handler {
|
pub struct Handler {
|
||||||
pub timer_running: AtomicBool,
|
pub timer_running: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for Handler {
|
||||||
|
async fn listen(ctx: &Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<()> {
|
||||||
|
match event {
|
||||||
|
Event::Ready { data_about_bot } => Handler::standard_startup(&ctx, &framework_ctx, data_about_bot).await,
|
||||||
|
Event::GuildBanAddition { guild_id, banned_user } => Handler::ban_add(&ctx, &framework_ctx, guild_id, banned_user).await,
|
||||||
|
Event::GuildBanRemoval { guild_id, unbanned_user } => Handler::ban_remove(&ctx, &framework_ctx, guild_id, unbanned_user).await,
|
||||||
|
Event::GuildMemberAddition { new_member } => Handler::new_member(&ctx, &framework_ctx, &new_member).await,
|
||||||
|
Event::GuildMemberRemoval { guild_id, user, member_data_if_available } => Handler::member_leave(&ctx, &framework_ctx, guild_id, user, member_data_if_available).await,
|
||||||
|
Event::GuildMemberUpdate { old_if_available, new } => Handler::member_update(&ctx, &framework_ctx, old_if_available, new).await,
|
||||||
|
Event::GuildRoleCreate { new } => Handler::new_role(&ctx, &framework_ctx, new).await,
|
||||||
|
Event::GuildRoleDelete { guild_id, removed_role_id, removed_role_data_if_available } => Handler::delete_role(&ctx, &framework_ctx, guild_id, removed_role_id, removed_role_data_if_available).await,
|
||||||
|
Event::GuildRoleUpdate { old_data_if_available, new } => Handler::update_role(&ctx, &framework_ctx, old_data_if_available, new).await,
|
||||||
|
Event::Message { new_message } => Handler::message(&ctx, &framework_ctx, &new_message).await,
|
||||||
|
Event::MessageDelete { channel_id, deleted_message_id, guild_id } => Handler::message_deleted(&ctx, &framework_ctx, channel_id, deleted_message_id, guild_id).await,
|
||||||
|
Event::MessageUpdate { old_if_available, new, event: _event } => Handler::message_edited(&ctx, &framework_ctx, old_if_available, new).await,
|
||||||
|
_ => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Handler {
|
impl Handler {
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Handler {
|
Handler {
|
||||||
timer_running: AtomicBool::from(false)
|
timer_running: AtomicBool::from(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn listen(ctx: &Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<()> {
|
|
||||||
match event {
|
|
||||||
Event::Ready { data_about_bot} => Handler::standard_startup(&ctx, &framework_ctx, data_about_bot).await,
|
|
||||||
Event::Message { new_message } => Handler::message(&ctx, &framework_ctx, &new_message).await,
|
|
||||||
Event::MessageUpdate { old_if_available, new, event: _event } => Handler::message_edited(&ctx, &framework_ctx, old_if_available, new).await,
|
|
||||||
_ => Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn standard_startup(ctx: &Context, framework_ctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, data_about_bot: &Ready) -> ManifoldResult<()> {
|
pub async fn standard_startup(ctx: &Context, framework_ctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, data_about_bot: &Ready) -> ManifoldResult<()> {
|
||||||
let config = &framework_ctx.user_data().await.bot_config;
|
let config = &framework_ctx.user_data().await.bot_config;
|
||||||
let responses = &framework_ctx.user_data().await.responses;
|
let responses = &framework_ctx.user_data().await.responses;
|
||||||
|
|
@ -40,10 +57,8 @@ impl Handler {
|
||||||
None => "Manifold bot connected to discord and ready to begin broadcast operations.".to_string(),
|
None => "Manifold bot connected to discord and ready to begin broadcast operations.".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let bot_nickname = config.get_value(&"BotNickname".to_string()).unwrap_or("BrokenManifoldBot".to_string());
|
|
||||||
let channel: ChannelId = config.get_channel(&"Log".to_string()).expect("Specified log channel invalid or unavailable");
|
|
||||||
for guild in &data_about_bot.guilds {
|
for guild in &data_about_bot.guilds {
|
||||||
match guild.id.edit_nickname(&ctx, Some(&*bot_nickname)).await {
|
match guild.id.edit_nickname(&ctx, Some(&*config.nickname)).await {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error setting bot nickname (lack permission?): {:?}", e);
|
error!("Error setting bot nickname (lack permission?): {:?}", e);
|
||||||
|
|
@ -51,7 +66,142 @@ impl Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.say(&ctx, greeting).await.expect("Couldn't message log channel!");
|
config.channels.log.say(&ctx, greeting).await.expect("Couldn't message log channel!");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ban_add(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, banned_user: &User) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("{} was banned", banned_user.name))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ban_remove(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, unbanned_user: &User) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("Ban was lifted for {}", unbanned_user.name))
|
||||||
|
.colour(Colour::from_rgb(0, 255, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_member(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, new_member: &Member) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("{} joined the server with nickname {} ({})", new_member.user.name, new_member.nick.as_ref().unwrap_or(&new_member.user.name), new_member.user.id))
|
||||||
|
.colour(Colour::from_rgb(0, 255, 0))
|
||||||
|
.timestamp(new_member.joined_at.unwrap_or(Timestamp::now()))
|
||||||
|
.field("Account creation date", new_member.user.created_at(), false)
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn member_leave(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, user: &User, _member_data_if_available: &Option<Member>) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("{} left the server", user.name))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn member_update(_ctx: &Context, _fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _old_if_available: &Option<Member>, _new: &Member) -> ManifoldResult<()> {
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_role(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, new: &Role) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("New role {} ({}) was created", &new.name, &new.id))
|
||||||
|
.colour(Colour::from_rgb(0, 255, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
.field("Role Permissions", &new.permissions, false)
|
||||||
|
.field("Hoist", &new.hoist, true)
|
||||||
|
.field("Icon", &new.icon.clone().unwrap_or("None set".to_string()), true)
|
||||||
|
.field("Mentionable", &new.mentionable, true)
|
||||||
|
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn delete_role(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _guild_id: &GuildId, removed_role_id: &RoleId, removed_role_data_if_available: &Option<Role>) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
|
||||||
|
match removed_role_data_if_available {
|
||||||
|
Some(role) => {
|
||||||
|
return e
|
||||||
|
.title(format!("Role {} ({}) was deleted", &role.name, &role.id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
.field("Role Permissions", &role.permissions, false)
|
||||||
|
.field("Hoist", &role.hoist, true)
|
||||||
|
.field("Icon", &role.icon.clone().unwrap_or("None set".to_string()), true)
|
||||||
|
.field("Mentionable", &role.mentionable, true)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
return e
|
||||||
|
.title(format!("Role {} was deleted (Role was not cached)", removed_role_id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async fn update_role(_ctx: &Context, _fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, _old_data_if_available: &Option<Role>, _new: &Role) -> ManifoldResult<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -77,23 +227,79 @@ impl Handler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn message_edited(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, original: &Option<Message>, new_message: &Option<Message>) -> ManifoldResult<()> {
|
async fn message_deleted(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, channel_id: &ChannelId, deleted_message_id: &MessageId, _guild_id: &Option<GuildId>) -> ManifoldResult<()> {
|
||||||
let log_channel = fctx.user_data().await.bot_config.get_channel(&"Log".to_string()).unwrap();
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
let channel = match ctx.cache.guild_channel(channel_id) {
|
||||||
|
Some(c) => c.name,
|
||||||
|
None => format!("Not Available / Not Cached ({})", channel_id)
|
||||||
|
};
|
||||||
|
|
||||||
|
match ctx.cache.message(channel_id, deleted_message_id) {
|
||||||
|
Some(msg) => {
|
||||||
|
|
||||||
|
let mut attachment_urls: Vec<String> = Vec::new();
|
||||||
|
for attachment in &msg.attachments {
|
||||||
|
attachment_urls.push(attachment.url.clone());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(new) = new_message.as_ref() {
|
|
||||||
log_channel.send_message(ctx, |f| {
|
log_channel.send_message(ctx, |f| {
|
||||||
f
|
f
|
||||||
.content("")
|
.content("")
|
||||||
.embed(|e| {
|
.embed(|e| {
|
||||||
e
|
e
|
||||||
.title("Message updated")
|
.title(format!("Message removed in #{} ({})", channel, channel_id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.author(|a| a.name(&msg.author.name))
|
||||||
|
.field("Message Content", msg.content_safe(&ctx.cache), false)
|
||||||
|
.field("Message Attachments", attachment_urls.join(", "), false)
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
.footer(|f| f.text(&msg.author.id));
|
||||||
|
for attachment in &msg.attachments {
|
||||||
|
e.image(attachment.url.clone());
|
||||||
|
}
|
||||||
|
e
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("Message removed in #{} ({}) (Not cached)", channel, channel_id))
|
||||||
|
.colour(Colour::from_rgb(255, 0, 0))
|
||||||
|
.timestamp(Timestamp::now())
|
||||||
|
})
|
||||||
|
}).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn message_edited(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, original: &Option<Message>, new_message: &Option<Message>) -> ManifoldResult<()> {
|
||||||
|
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||||
|
|
||||||
|
if let Some(new) = new_message.as_ref() {
|
||||||
|
let message_channel = new.channel_id.name(ctx).await.unwrap_or("Unknown Channel".to_string());
|
||||||
|
log_channel.send_message(ctx, |f| {
|
||||||
|
f
|
||||||
|
.content("")
|
||||||
|
.embed(|e| {
|
||||||
|
e
|
||||||
|
.title(format!("Message updated in #{} ({})", message_channel, new.channel_id))
|
||||||
|
.colour(Colour::from_rgb(255, 153, 0))
|
||||||
.author(|a| a.name(new.author.name.clone()))
|
.author(|a| a.name(new.author.name.clone()))
|
||||||
.timestamp(new.timestamp)
|
.timestamp(Timestamp::now())
|
||||||
.field("Original Content", match original.as_ref() {
|
.field("Original Content", match original.as_ref() {
|
||||||
Some(m) => m.content.clone(),
|
Some(m) => m.content.clone(),
|
||||||
None => "Not available".to_string(),
|
None => "Not available".to_string(),
|
||||||
}, false)
|
}, false)
|
||||||
.field("New Content", new.content.clone(), false)
|
.field("New Content", new.content.clone(), false)
|
||||||
|
.field("Message created at", new.timestamp, true)
|
||||||
|
.field("Message author", &new.author.id, true)
|
||||||
})
|
})
|
||||||
}).await?;
|
}).await?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
src/lib.rs
43
src/lib.rs
|
|
@ -16,9 +16,8 @@ use poise::serenity_prelude::*;
|
||||||
|
|
||||||
use crate::config::ManifoldConfig;
|
use crate::config::ManifoldConfig;
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
use crate::events::Handler;
|
use crate::events::EventHandler;
|
||||||
use crate::models::user::{ManifoldUserInfo, UserInfo};
|
use crate::models::user::{ManifoldUserInfo, UserInfo};
|
||||||
use crate::models::fueltank::FuelTank;
|
|
||||||
use crate::responses::Responses;
|
use crate::responses::Responses;
|
||||||
|
|
||||||
pub mod helpers;
|
pub mod helpers;
|
||||||
|
|
@ -62,26 +61,23 @@ impl Deref for ManifoldData {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ManifoldDataInner {
|
pub struct ManifoldDataInner {
|
||||||
bot_config: ManifoldConfig,
|
pub bot_config: ManifoldConfig,
|
||||||
database: Db,
|
pub database: Db,
|
||||||
responses: Responses,
|
pub responses: Responses,
|
||||||
user_info: Mutex<ManifoldUserInfo>,
|
pub user_info: Mutex<ManifoldUserInfo>,
|
||||||
version_string: String,
|
pub version_string: String,
|
||||||
frogtip_fueltank: Mutex<FuelTank>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ManifoldContext<'a> = poise::Context<'a, ManifoldData, ManifoldError>;
|
pub type ManifoldContext<'a> = poise::Context<'a, ManifoldData, ManifoldError>;
|
||||||
pub type ManifoldCommand = poise::Command<ManifoldData, ManifoldError>;
|
pub type ManifoldCommand = poise::Command<ManifoldData, ManifoldError>;
|
||||||
|
|
||||||
pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, injected_commands: Vec<ManifoldCommand>, caller_version_string: String) -> ManifoldResult<FrameworkBuilder<ManifoldData, ManifoldError>> {
|
pub async fn prepare_client<T: EventHandler>(arguments: ArgMatches, intents: GatewayIntents, injected_commands: Vec<ManifoldCommand>, caller_version_string: String, caller_database_migrations: EmbeddedMigrations) -> ManifoldResult<FrameworkBuilder<ManifoldData, ManifoldError>> {
|
||||||
let bot_environment = arguments.get_one("environment").unwrap();
|
let bot_environment = arguments.get_one("environment").unwrap();
|
||||||
let config_file = format!("config/{}", arguments.get_one::<String>("config-file").unwrap());
|
let config_file = arguments.get_one::<String>("config-file").unwrap();
|
||||||
|
|
||||||
info!("Reading configuration...");
|
info!("Reading configuration...");
|
||||||
debug!("Configuration file path: {}", &config_file);
|
debug!("Configuration file path: {}", &config_file);
|
||||||
let config = ManifoldConfig::load_config(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file));
|
let bot_config = ManifoldConfig::new(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file));
|
||||||
|
|
||||||
let prefix = config.get_value(&"BotPrefix".to_string()).expect("Could not read bot_prefix from config.");
|
|
||||||
|
|
||||||
let manager = ConnectionManager::<SqliteConnection>::new("manifold.db");
|
let manager = ConnectionManager::<SqliteConnection>::new("manifold.db");
|
||||||
let pool = r2d2::Pool::builder()
|
let pool = r2d2::Pool::builder()
|
||||||
|
|
@ -90,7 +86,7 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
.expect("Database setup error!");
|
.expect("Database setup error!");
|
||||||
|
|
||||||
let mut responses = Responses::new();
|
let mut responses = Responses::new();
|
||||||
responses.reload(&PathBuf::from(config.get_value(&"ResponsesFilePath".to_string()).unwrap_or("txt/responses.txt".to_string()))).expect("Could not load responses file!");
|
responses.reload(&PathBuf::from(&bot_config.responses_file_path)).expect("Could not load responses file!");
|
||||||
|
|
||||||
let token = env::var("DISCORD_TOKEN").expect(
|
let token = env::var("DISCORD_TOKEN").expect(
|
||||||
"Could not find an environment variable called DISCORD_TOKEN",
|
"Could not find an environment variable called DISCORD_TOKEN",
|
||||||
|
|
@ -99,17 +95,15 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
let framework = poise::Framework::builder()
|
let framework = poise::Framework::builder()
|
||||||
.options(poise::FrameworkOptions {
|
.options(poise::FrameworkOptions {
|
||||||
event_handler: |ctx, e, fctx, _| Box::pin(async move {
|
event_handler: |ctx, e, fctx, _| Box::pin(async move {
|
||||||
Handler::listen(ctx, fctx, e).await
|
T::listen(ctx, fctx, e).await
|
||||||
}),
|
}),
|
||||||
pre_command: |ctx: ManifoldContext<'_>| Box::pin(async move {
|
pre_command: |ctx: ManifoldContext<'_>| Box::pin(async move {
|
||||||
info!("Received command {} from {}", ctx.command().name, ctx.author().name);
|
info!("Received command {} from {}", ctx.command().name, ctx.author().name);
|
||||||
let config = &ctx.data().bot_config;
|
let _ = ctx.data().bot_config.channels.log.say(ctx, format!("Received command {} from {}", ctx.command().name, ctx.author().name)).await;
|
||||||
let log_channel = config.get_channel(&"Log".to_string()).unwrap();
|
|
||||||
let _ = log_channel.say(ctx, format!("Received command {} from {}", ctx.command().name, ctx.author().name)).await;
|
|
||||||
}),
|
}),
|
||||||
commands: commands::collect_commands(injected_commands),
|
commands: commands::collect_commands(injected_commands),
|
||||||
prefix_options: poise::PrefixFrameworkOptions {
|
prefix_options: poise::PrefixFrameworkOptions {
|
||||||
prefix: Some(prefix),
|
prefix: Some(bot_config.prefix.clone()),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|
@ -120,17 +114,18 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||||
ctx.set_activity(Activity::watching("you")).await;
|
ctx.set_activity(Activity::watching("you")).await;
|
||||||
|
ctx.cache.set_max_messages(350);
|
||||||
let db = Db { pool };
|
let db = Db { pool };
|
||||||
apply_migrations(&mut db.get()?);
|
apply_migrations(&mut db.get()?, caller_database_migrations);
|
||||||
let user_info = UserInfo::load(&db).expect("Could not load user info, rejecting");
|
let user_info = UserInfo::load(&db).expect("Could not load user info, rejecting");
|
||||||
let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string();
|
let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string();
|
||||||
|
|
||||||
Ok(ManifoldData(Arc::new(ManifoldDataInner {
|
Ok(ManifoldData(Arc::new(ManifoldDataInner {
|
||||||
bot_config: config,
|
bot_config,
|
||||||
database: db,
|
database: db,
|
||||||
responses,
|
responses,
|
||||||
user_info: Mutex::new(user_info),
|
user_info: Mutex::new(user_info),
|
||||||
version_string: format!("{caller} (Manifold framework version {mfold_ver} built at {mfold_time} from revision {mfold_rev})", caller=caller_version_string, mfold_ver=built_info::PKG_VERSION, mfold_time=built_info::BUILT_TIME_UTC, mfold_rev=git_info),
|
version_string: format!("{caller} (Manifold framework version {mfold_ver} built at {mfold_time} from revision {mfold_rev})", caller=caller_version_string, mfold_ver=built_info::PKG_VERSION, mfold_time=built_info::BUILT_TIME_UTC, mfold_rev=git_info),
|
||||||
frogtip_fueltank: Mutex::new(FuelTank::with_cache())
|
|
||||||
})))
|
})))
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
@ -138,8 +133,10 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
||||||
Ok(framework)
|
Ok(framework)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_migrations(conn: &mut impl MigrationHarness<Sqlite>) {
|
fn apply_migrations(conn: &mut impl MigrationHarness<Sqlite>, caller_migrations: EmbeddedMigrations) {
|
||||||
|
|
||||||
conn.run_pending_migrations(MIGRATIONS)
|
conn.run_pending_migrations(MIGRATIONS)
|
||||||
.expect("An error occurred applying migrations.");
|
.expect("An error occurred applying migrations.");
|
||||||
|
conn.run_pending_migrations(caller_migrations)
|
||||||
|
.expect("An error occurred applying caller migrations");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::models::fueltank::Pump;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct AnimalPic {
|
||||||
|
pub id: String,
|
||||||
|
pub url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AnimalPicWrapper = Vec<AnimalPic>;
|
||||||
|
|
||||||
|
impl Display for AnimalPic {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pump<AnimalPic> for AnimalPicWrapper {
|
||||||
|
fn pump(&mut self) -> Option<AnimalPic> {
|
||||||
|
self.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::models::fueltank::Pump;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DadJoke {
|
||||||
|
pub id: String,
|
||||||
|
pub joke: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DadJokeWrapper {
|
||||||
|
pub results: Vec<DadJoke>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for DadJoke {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.joke)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pump<DadJoke> for DadJokeWrapper {
|
||||||
|
fn pump(&mut self) -> Option<DadJoke> {
|
||||||
|
self.results.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.results.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::models::fueltank::Pump;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Tip {
|
||||||
|
pub number: i32,
|
||||||
|
pub tip: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Tip {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "#{}: {}", self.number, self.tip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Croak {
|
||||||
|
pub tips: Vec<Tip>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pump<Tip> for Croak {
|
||||||
|
fn pump(&mut self) -> Option<Tip> {
|
||||||
|
self.tips.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
self.tips.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,84 +1,57 @@
|
||||||
use std::fmt::{Display, Formatter};
|
use std::env::temp_dir;
|
||||||
|
use std::fmt::Debug;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use dirs::home_dir;
|
|
||||||
use std::io::{SeekFrom, Seek};
|
use std::io::{SeekFrom, Seek};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::{ACCEPT, CONTENT_TYPE};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
|
||||||
const CACHE_VERSION: &'static str = "1";
|
const CACHE_VERSION: &'static str = "1";
|
||||||
|
|
||||||
pub trait Pump<V> {
|
pub trait Pump<V> {
|
||||||
fn pump<F>(self) -> Result<V, ManifoldError>;
|
fn pump(&mut self) -> Option<V>;
|
||||||
|
fn len(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct FuelTank {
|
pub struct FuelTank {
|
||||||
mode: TankMode,
|
pub(crate) source_uri: String,
|
||||||
|
cache_name: Option<String>,
|
||||||
|
pub(crate) api_key: Option<String>,
|
||||||
|
cache_mode: TankMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||||
enum TankMode {
|
pub enum TankMode {
|
||||||
Cache,
|
Cache,
|
||||||
|
NoCache,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuelTank {
|
impl FuelTank {
|
||||||
pub fn with_cache() -> Self {
|
pub async fn next_or_fill<T: Debug, U: Debug>(&self) -> ManifoldResult<T>
|
||||||
Self {
|
where T: DeserializeOwned,
|
||||||
mode: TankMode::Cache,
|
U: Serialize,
|
||||||
}
|
U: DeserializeOwned, U: Pump<T>
|
||||||
}
|
{
|
||||||
}
|
let path = PathBuf::new()
|
||||||
|
.join(temp_dir())
|
||||||
|
.join(format!("manifold-{}-cache-{}.json", self.cache_name.clone().unwrap(), CACHE_VERSION));
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Debug)]
|
let mut fuel = match self.cache_mode {
|
||||||
pub struct Tip {
|
TankMode::Cache => {
|
||||||
pub number: i32,
|
// Read cache only if we have one
|
||||||
pub tip: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Tip {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "#{}: {}", self.number, self.tip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct Croak {
|
|
||||||
pub tips: Vec<Tip>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn croak() -> ManifoldResult<Vec<Tip>> {
|
|
||||||
|
|
||||||
debug!("Getting new tips");
|
|
||||||
let result: Croak;
|
|
||||||
|
|
||||||
let client = Client::new();
|
|
||||||
result = client.get("https://frog.tips/api/1/tips/")
|
|
||||||
.header(CONTENT_TYPE, "application/json")
|
|
||||||
.send().await?.json().await?;
|
|
||||||
|
|
||||||
debug!("Result: {:?}", &result);
|
|
||||||
|
|
||||||
Ok(result.tips)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FuelTank {
|
|
||||||
pub async fn next_or_fill(self) -> Result<Tip, ManifoldError> {
|
|
||||||
debug!("Filling tank!");
|
debug!("Filling tank!");
|
||||||
let mut cache = {
|
let mut cache = {
|
||||||
let mut path = PathBuf::new();
|
|
||||||
path.push(home_dir().ok_or(ManifoldError::from("No home dir"))?);
|
|
||||||
path.push(".local");
|
|
||||||
path.push(format!("coffeebot-frog-cache-{}.json", CACHE_VERSION));
|
|
||||||
debug!("Cache path: {:?}", &path);
|
debug!("Cache path: {:?}", &path);
|
||||||
fs::OpenOptions::new()
|
fs::OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
.open(path)
|
.open(&path)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("Error creating cache file: {}", e);
|
error!("Error creating cache file: {}", e);
|
||||||
ManifoldError::from("CACHE ERROR")
|
ManifoldError::from("CACHE ERROR")
|
||||||
|
|
@ -87,29 +60,69 @@ impl FuelTank {
|
||||||
|
|
||||||
debug!("Cache: {:?}", &cache);
|
debug!("Cache: {:?}", &cache);
|
||||||
|
|
||||||
let json: Result<Vec<Tip>, serde_json::Error> = serde_json::from_reader(&mut cache);
|
let json: Result<U, serde_json::Error> = serde_json::from_reader(&mut cache);
|
||||||
debug!("Json: {:?}", &json);
|
debug!("Json: {:?}", &json);
|
||||||
let mut tips = match json {
|
match json {
|
||||||
Ok(contents) => {
|
Ok(contents) => {
|
||||||
match contents.len() {
|
match contents.len() {
|
||||||
0 => croak().await?,
|
0 => self.emit().await?,
|
||||||
_ => contents,
|
_ => contents,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => croak().await?,
|
_ => self.emit().await?,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
TankMode::NoCache => {
|
||||||
|
self.emit().await?
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Got tips: {:?}", &tips);
|
debug!("Got tips: {:?}", &fuel);
|
||||||
let tip = tips.pop().ok_or(ManifoldError::from("FROG HAS NO TIPS FOR YOU. CROAK ERROR 12"))?;
|
let single = fuel.pump().ok_or(ManifoldError::from("CACHE ERROR"))?;
|
||||||
|
|
||||||
debug!("Got single tip: {:?}", &tip);
|
debug!("Got single tip: {:?}", &single);
|
||||||
|
if self.cache_mode == TankMode::Cache {
|
||||||
|
// Write cache only if we have one
|
||||||
|
let mut cache = {
|
||||||
|
debug!("Cache path: {:?}", &path);
|
||||||
|
fs::OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&path)
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("Error creating cache file: {}", e);
|
||||||
|
ManifoldError::from("CACHE ERROR")
|
||||||
|
})?
|
||||||
|
};
|
||||||
cache
|
cache
|
||||||
.set_len(0)
|
.set_len(0)
|
||||||
.and_then(|_| cache.seek(SeekFrom::Start(0)))?;
|
.and_then(|_| cache.seek(SeekFrom::Start(0)))?;
|
||||||
|
|
||||||
debug!("Writing cache");
|
debug!("Writing cache");
|
||||||
serde_json::to_writer(cache, &tips)?;
|
serde_json::to_writer(cache, &fuel)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(tip)
|
return Ok(single)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn emit<U: Debug>(&self) -> ManifoldResult<U>
|
||||||
|
where U: DeserializeOwned
|
||||||
|
{
|
||||||
|
debug!("Getting new tips");
|
||||||
|
let result: U;
|
||||||
|
|
||||||
|
let mut client = Client::new().get(&self.source_uri)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.header(ACCEPT, "application/json");
|
||||||
|
|
||||||
|
if let Some(api_key) = &self.api_key {
|
||||||
|
client = client.header("X-API-KEY", api_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
result = client.send().await?.json().await?;
|
||||||
|
|
||||||
|
debug!("Result: {:?}", &result);
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
pub mod animalpics;
|
||||||
|
pub mod dadjoke;
|
||||||
|
pub mod frogtip;
|
||||||
pub mod fueltank;
|
pub mod fueltank;
|
||||||
|
pub mod nasa;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod weather;
|
pub mod weather;
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
use crate::models::fueltank::Pump;
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize, Debug)]
|
||||||
|
pub struct NasaAstroPhoto {
|
||||||
|
pub explanation: String,
|
||||||
|
pub hdurl: String,
|
||||||
|
pub title: String
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for NasaAstroPhoto {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.hdurl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pump<NasaAstroPhoto> for NasaAstroPhoto {
|
||||||
|
fn pump(&mut self) -> Option<NasaAstroPhoto> {
|
||||||
|
Some(self.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::schema::userinfo::dsl as userinfo_dsl;
|
||||||
use crate::Db;
|
use crate::Db;
|
||||||
use crate::error::{ManifoldError, ManifoldResult};
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
|
|
||||||
#[derive(Identifiable, Insertable, AsChangeset, Debug, Queryable, Serialize, Clone)]
|
#[derive(Identifiable, Insertable, AsChangeset, Selectable, Debug, Queryable, Serialize, Clone)]
|
||||||
#[diesel(primary_key(user_id))]
|
#[diesel(primary_key(user_id))]
|
||||||
#[diesel(table_name = userinfo)]
|
#[diesel(table_name = userinfo)]
|
||||||
pub struct UserInfo {
|
pub struct UserInfo {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
use crate::error::ManifoldResult;
|
use crate::error::{ManifoldError, ManifoldResult};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use reqwest::header::CONTENT_TYPE;
|
use reqwest::header::CONTENT_TYPE;
|
||||||
|
use crate::models::fueltank::FuelTank;
|
||||||
pub struct Weather {
|
|
||||||
base_url: String,
|
|
||||||
api_key: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct CurrentConditionInformation {
|
pub struct CurrentConditionInformation {
|
||||||
|
|
@ -145,22 +141,13 @@ pub struct WeatherForecastRequestResponse {
|
||||||
pub forecast: ForecastDayWrapper,
|
pub forecast: ForecastDayWrapper,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Weather = FuelTank;
|
||||||
|
|
||||||
impl Weather {
|
impl Weather {
|
||||||
pub fn new(api_key:String, base_url: String) -> Self {
|
|
||||||
Weather {
|
|
||||||
base_url,
|
|
||||||
api_key,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_weather_forecast(&self, target: String, days: Option<i32>) -> ManifoldResult<WeatherForecastRequestResponse> {
|
pub async fn get_weather_forecast(&self, target: String) -> ManifoldResult<WeatherForecastRequestResponse> {
|
||||||
let target_url: String;
|
|
||||||
|
|
||||||
if let Some(d) = days {
|
let target_url = format!("{}/forecast.json?key={}&q={}&days=2", self.source_uri, self.api_key.clone().ok_or_else(|| ManifoldError::from("No API key presented for Weather API"))?, &target);
|
||||||
target_url = format!("{}/forecast.json?key={}&q={}&days={}", self.base_url, self.api_key, &target, d);
|
|
||||||
} else {
|
|
||||||
target_url = format!("{}/forecast.json?key={}&q={}&days=3", self.base_url, self.api_key, &target);
|
|
||||||
}
|
|
||||||
|
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
let result = client.get(target_url)
|
let result = client.get(target_url)
|
||||||
|
|
@ -179,7 +166,7 @@ impl Weather {
|
||||||
decoded_result.current.dewpoint_f = Some((decoded_result.current.dewpoint_c.unwrap() * 1.8) + 32.0);
|
decoded_result.current.dewpoint_f = Some((decoded_result.current.dewpoint_c.unwrap() * 1.8) + 32.0);
|
||||||
|
|
||||||
|
|
||||||
debug!("{:?}", decoded_result);
|
debug!("{:?}", &decoded_result);
|
||||||
|
|
||||||
Ok(decoded_result)
|
Ok(decoded_result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue