Merge branch 'feature/weather-forecasting'
This commit is contained in:
commit
57b79e7a5e
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "manifold"
|
||||
version = "2.0.0"
|
||||
version = "3.0.0"
|
||||
authors = ["Lucy Bladen <admin@lbladen.uk>"]
|
||||
edition = "2021"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,32 +4,7 @@ use crate::error::ManifoldError;
|
|||
|
||||
#[poise::command(slash_command, prefix_command, owners_only)]
|
||||
async fn dump_config(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||
let config = &ctx.data().bot_config;
|
||||
|
||||
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?;
|
||||
ctx.say(format!("Config dump; {:?}", &ctx.data().bot_config)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -77,6 +52,6 @@ async fn save_world(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 6] {
|
||||
[dump_config(), get_environment(), get_config(), register_commands(), set_activity(), save_world()]
|
||||
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 4] {
|
||||
[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()]
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
mod admin;
|
||||
mod frog;
|
||||
mod animal;
|
||||
mod convert;
|
||||
mod core;
|
||||
mod weather;
|
||||
|
|
@ -9,7 +9,7 @@ use crate::ManifoldCommand;
|
|||
pub fn collect_commands(injected: Vec<ManifoldCommand>) -> Vec<ManifoldCommand> {
|
||||
core::commands().into_iter()
|
||||
.chain(admin::commands())
|
||||
.chain(frog::commands())
|
||||
.chain(animal::commands())
|
||||
.chain(convert::commands())
|
||||
.chain(weather::commands())
|
||||
.chain(injected)
|
||||
|
|
|
|||
|
|
@ -1,99 +1,59 @@
|
|||
use chrono::{NaiveDateTime, Timelike};
|
||||
use poise::serenity_prelude::utils::Colour;
|
||||
use d20::roll_range;
|
||||
use poise::ReplyHandle;
|
||||
use poise::serenity_prelude::CreateEmbed;
|
||||
use crate::{ManifoldCommand, ManifoldContext};
|
||||
|
||||
use crate::models::weather::{Weather, WeatherForecastRequestResponse};
|
||||
use crate::models::weather::WeatherForecastRequestResponse;
|
||||
use crate::models::user::UserInfo;
|
||||
use crate::error::ManifoldResult;
|
||||
use crate::error::{ManifoldError, ManifoldResult};
|
||||
use crate::helpers::paginate;
|
||||
|
||||
#[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<()> {
|
||||
let my_message = ctx.say("Retrieving weather, be patient").await?;
|
||||
|
||||
let weather_forecast = _get_weather(ctx, &my_message, location, Some(1)).await?;
|
||||
let weather_forecast = _get_weather(ctx, &my_message, location).await?;
|
||||
|
||||
let responses = &ctx.data().responses;
|
||||
|
||||
my_message.edit(ctx, |m| {
|
||||
m.content("");
|
||||
m.embed(|e| {
|
||||
let mut pages = Vec::<CreateEmbed>::new();
|
||||
|
||||
let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8);
|
||||
let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8);
|
||||
|
||||
e.colour(card_colour);
|
||||
e.title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country));
|
||||
e.description(format!("Observations recorded at {}.", weather_forecast.current.last_updated));
|
||||
e.image(format!("https:{}", weather_forecast.current.condition.icon));
|
||||
e.fields(vec![
|
||||
("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", weather_forecast.current.temp_c, weather_forecast.current.temp_f, weather_forecast.current.dewpoint_c.unwrap_or(0.0), weather_forecast.current.dewpoint_f.unwrap_or(0.0)), true),
|
||||
("Feels like", format!("{}°C/{}°F", weather_forecast.current.feelslike_c, weather_forecast.current.feelslike_f), true),
|
||||
("Condition", format!("{}", weather_forecast.current.condition.text), true),
|
||||
("Pressure", format!("{}mb/{}in", weather_forecast.current.pressure_mb, weather_forecast.current.pressure_in), true),
|
||||
("Precipitation", format!("{}mm/{}in", weather_forecast.current.precip_mm, weather_forecast.current.precip_in), true),
|
||||
("Humidity", format!("{}%", weather_forecast.current.humidity), true),
|
||||
("Cloud coverage", format!("{}%", weather_forecast.current.cloud), true),
|
||||
("UV index", format!("{}", weather_forecast.current.uv), true),
|
||||
("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true),
|
||||
]);
|
||||
e.field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", weather_forecast.current.wind_mph, weather_forecast.current.wind_kph, weather_forecast.current.wind_dir, weather_forecast.current.wind_degree, weather_forecast.current.gust_mph, weather_forecast.current.gust_kph), false);
|
||||
e.footer(|f| {
|
||||
f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string())));
|
||||
f
|
||||
});
|
||||
e
|
||||
});
|
||||
m
|
||||
}).await?;
|
||||
weather_forecast.forecast.forecastday.iter().for_each(|d| {
|
||||
d.hour.iter().for_each(|f| {
|
||||
pages.push(CreateEmbed::default()
|
||||
.colour(card_colour)
|
||||
.title(format!("Current weather at {}, {}, {}", weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country))
|
||||
.description(format!("Forecast for {} made at {}.", f.time, weather_forecast.current.last_updated))
|
||||
.image(format!("https:{}", f.condition.icon))
|
||||
.fields(vec![
|
||||
("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", f.temp_c, f.temp_f, f.dewpoint_c, f.dewpoint_f), true),
|
||||
("Feels like", format!("{}°C/{}°F", f.feelslike_c, f.feelslike_f), true),
|
||||
("Condition", format!("{}", f.condition.text), true),
|
||||
("Pressure", format!("{}mb/{}in", f.pressure_mb, f.pressure_in), true),
|
||||
("Precipitation", format!("{}mm/{}in", f.precip_mm, f.precip_in), true),
|
||||
("Humidity", format!("{}%", f.humidity), true),
|
||||
("Cloud coverage", format!("{}%", f.cloud), true),
|
||||
("Coordinates", format!("Lat: {} Lon: {}", weather_forecast.location.lat, weather_forecast.location.lon), true),
|
||||
])
|
||||
.field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", f.wind_mph, f.wind_kph, f.wind_dir, f.wind_degree, f.gust_mph, f.gust_kph), false)
|
||||
.footer(|f| {
|
||||
f.text(format!("{}", responses.get_response(&"weather card footer".to_string()).unwrap_or(&"Weather Powered By Deez Nutz".to_string())));
|
||||
f
|
||||
}).to_owned());
|
||||
})
|
||||
});
|
||||
|
||||
let now = NaiveDateTime::parse_from_str(weather_forecast.current.last_updated.as_str(), "%Y-%m-%d %H:%M").unwrap_or(NaiveDateTime::default());
|
||||
|
||||
paginate(ctx, my_message, pages, (now.hour() + 1) as usize).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/*
|
||||
#[command]
|
||||
#[aliases("wf")]
|
||||
pub async fn weather_forecast(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
|
||||
|
||||
let mut my_message = msg.reply_ping(&ctx, "Retrieving weather, be patient").await?;
|
||||
|
||||
let weather_forecast = _get_weather(ctx, msg, args, Some(3)).await?;
|
||||
|
||||
let forecast_index: usize = (chrono::NaiveDateTime::parse_from_str(&*weather_forecast.location.localtime, "%Y-%m-%d %H:%M").unwrap().hour() as usize) + 1;
|
||||
|
||||
debug!("{:?}", weather_forecast);
|
||||
|
||||
my_message.edit(&ctx, |m| {
|
||||
m.content("");
|
||||
|
||||
for day in &weather_forecast.forecast.forecastday {
|
||||
m.add_embed(|e| {
|
||||
|
||||
let this_forecast = &day.hour[forecast_index];
|
||||
let card_colour = Colour::from_rgb(roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8, roll_range(0, 255).unwrap_or(0) as u8);
|
||||
|
||||
e.colour(card_colour);
|
||||
e.title(format!("Weather for {} at {}, {}, {}", this_forecast.time, weather_forecast.location.name, weather_forecast.location.region, weather_forecast.location.country));
|
||||
e.description(format!("Observations recorded at {}.", weather_forecast.current.last_updated));
|
||||
e.fields(vec![
|
||||
("Temperature (Dewpoint)", format!("{}°C/{}°F ({:.1}°C/{:.1}°F)", this_forecast.temp_c, this_forecast.temp_f, this_forecast.dewpoint_c, this_forecast.dewpoint_f), true),
|
||||
("Feels like", format!("{}°C/{}°F", this_forecast.feelslike_c, this_forecast.feelslike_f), true),
|
||||
("Condition", format!("{}", this_forecast.condition.text), true),
|
||||
("Pressure", format!("{}mb/{}in", this_forecast.pressure_mb, this_forecast.pressure_in), true),
|
||||
("Precipitation", format!("{}mm/{}in", this_forecast.precip_mm, this_forecast.precip_in), true),
|
||||
("Humidity", format!("{}%", this_forecast.humidity), true),
|
||||
]);
|
||||
e.field("Wind", format!("{}mph/{}kph from the {} ({} degrees), gusting to {}mph/{}kph", this_forecast.wind_mph, this_forecast.wind_kph, this_forecast.wind_dir, this_forecast.wind_degree, this_forecast.gust_mph, this_forecast.gust_kph), false);
|
||||
e
|
||||
});
|
||||
}
|
||||
|
||||
m
|
||||
}).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
*/
|
||||
|
||||
#[poise::command(slash_command, prefix_command, aliases("wl"))]
|
||||
pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[description="Your default weather location"] location: String) -> ManifoldResult<()> {
|
||||
|
||||
|
|
@ -121,7 +81,7 @@ pub async fn save_weather_location(ctx: ManifoldContext<'_>, #[rest] #[descripti
|
|||
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();
|
||||
|
||||
|
|
@ -130,7 +90,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
|||
|
||||
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 {
|
||||
weather_location = loc;
|
||||
|
|
@ -158,7 +118,7 @@ pub async fn _get_weather(ctx: ManifoldContext<'_>, message_handle: &ReplyHandle
|
|||
|
||||
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,
|
||||
Err(e) => {
|
||||
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 config::Config;
|
||||
use poise::serenity_prelude::model::prelude::{ChannelId, MessageId, RoleId};
|
||||
use poise::serenity_prelude::ChannelId;
|
||||
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 environment: String,
|
||||
pub path: PathBuf,
|
||||
pub config: Config,
|
||||
pub prefix: String,
|
||||
pub channels: Channels,
|
||||
pub nickname: String,
|
||||
pub services: HashMap<String, FuelTank>,
|
||||
pub responses_file_path: PathBuf,
|
||||
}
|
||||
|
||||
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()
|
||||
.set_override("BotEnvironment", bot_environment.clone())?
|
||||
.add_source(config::File::with_name(config_file))
|
||||
.add_source(config::Environment::with_prefix("MANIFOLD"))
|
||||
.add_source(config::File::with_name(&*format!("config/{}.{}", bot_environment.to_lowercase(), config_file)))
|
||||
.add_source(config::Environment::with_prefix("manifold"))
|
||||
.build()?;
|
||||
|
||||
Ok(Self {
|
||||
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>()?))
|
||||
Ok(settings.try_deserialize()?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ use std::sync::atomic::AtomicBool;
|
|||
use poise::{Event, FrameworkContext};
|
||||
use poise::serenity_prelude::model::{
|
||||
gateway::Ready,
|
||||
id::ChannelId,
|
||||
};
|
||||
use poise::serenity_prelude::{Context, Message};
|
||||
use crate::ManifoldData;
|
||||
|
|
@ -40,10 +39,8 @@ impl Handler {
|
|||
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 {
|
||||
match guild.id.edit_nickname(&ctx, Some(&*bot_nickname)).await {
|
||||
match guild.id.edit_nickname(&ctx, Some(&*config.nickname)).await {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
error!("Error setting bot nickname (lack permission?): {:?}", e);
|
||||
|
|
@ -51,7 +48,7 @@ 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(())
|
||||
}
|
||||
|
|
@ -78,7 +75,7 @@ impl Handler {
|
|||
}
|
||||
|
||||
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.get_channel(&"Log".to_string()).unwrap();
|
||||
let log_channel = fctx.user_data().await.bot_config.channels.log;
|
||||
|
||||
if let Some(new) = new_message.as_ref() {
|
||||
log_channel.send_message(ctx, |f| {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
use crate::error::ManifoldResult;
|
||||
use crate::ManifoldContext;
|
||||
use poise::{ReplyHandle, serenity_prelude};
|
||||
use poise::serenity_prelude::CreateEmbed;
|
||||
|
||||
pub async fn paginate(ctx: ManifoldContext<'_>, message: ReplyHandle<'_>, pages: Vec<CreateEmbed>, mut current_page: usize) -> ManifoldResult<()> {
|
||||
// Define some unique identifiers for the navigation buttons
|
||||
let ctx_id = ctx.id();
|
||||
let prev_button_id = format!("{}prev", ctx.id());
|
||||
let next_button_id = format!("{}next", ctx.id());
|
||||
|
||||
// Send the embed with the first page as content
|
||||
message.edit(ctx, |m| {
|
||||
m.embeds = vec![pages[current_page].to_owned()];
|
||||
m.components(|b| {
|
||||
b.create_action_row(|b| {
|
||||
b.create_button(|b| b.custom_id(&prev_button_id).emoji('◀'))
|
||||
.create_button(|b| b.custom_id(&next_button_id).emoji('▶'))
|
||||
})
|
||||
})
|
||||
}).await?;
|
||||
|
||||
// Loop through incoming interactions with the navigation buttons
|
||||
while let Some(press) = serenity_prelude::CollectComponentInteraction::new(ctx)
|
||||
// We defined our button IDs to start with `ctx_id`. If they don't, some other command's
|
||||
// button was pressed
|
||||
.filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string()))
|
||||
// Timeout when no navigation button has been pressed for 24 hours
|
||||
.timeout(std::time::Duration::from_secs(300))
|
||||
.await
|
||||
{
|
||||
// Depending on which button was pressed, go to next or previous page
|
||||
if press.data.custom_id == next_button_id {
|
||||
current_page += 1;
|
||||
if current_page >= pages.len() {
|
||||
current_page = 0;
|
||||
}
|
||||
} else if press.data.custom_id == prev_button_id {
|
||||
current_page = current_page.checked_sub(1).unwrap_or(pages.len() - 1);
|
||||
} else {
|
||||
// This is an unrelated button interaction
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the message with the new page contents
|
||||
press
|
||||
.create_interaction_response(ctx, |b| {
|
||||
b.kind(serenity_prelude::InteractionResponseType::UpdateMessage)
|
||||
.interaction_response_data(|b| b.set_embeds(vec![pages[current_page.clone()].to_owned()]))
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
22
src/lib.rs
22
src/lib.rs
|
|
@ -18,10 +18,9 @@ use crate::config::ManifoldConfig;
|
|||
use crate::error::{ManifoldError, ManifoldResult};
|
||||
use crate::events::Handler;
|
||||
use crate::models::user::{ManifoldUserInfo, UserInfo};
|
||||
use crate::models::fueltank::FuelTank;
|
||||
use crate::responses::Responses;
|
||||
|
||||
pub mod autocomplete_helpers;
|
||||
pub mod helpers;
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
pub mod events;
|
||||
|
|
@ -67,7 +66,6 @@ pub struct ManifoldDataInner {
|
|||
responses: Responses,
|
||||
user_info: Mutex<ManifoldUserInfo>,
|
||||
version_string: String,
|
||||
frogtip_fueltank: Mutex<FuelTank>,
|
||||
}
|
||||
|
||||
pub type ManifoldContext<'a> = poise::Context<'a, ManifoldData, ManifoldError>;
|
||||
|
|
@ -75,13 +73,11 @@ 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>> {
|
||||
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...");
|
||||
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 prefix = config.get_value(&"BotPrefix".to_string()).expect("Could not read bot_prefix from config.");
|
||||
let bot_config = ManifoldConfig::new(&config_file, bot_environment).expect(&*format!("Could not read configuration file {}", &config_file));
|
||||
|
||||
let manager = ConnectionManager::<SqliteConnection>::new("manifold.db");
|
||||
let pool = r2d2::Pool::builder()
|
||||
|
|
@ -90,7 +86,7 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
|||
.expect("Database setup error!");
|
||||
|
||||
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(
|
||||
"Could not find an environment variable called DISCORD_TOKEN",
|
||||
|
|
@ -103,13 +99,11 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
|||
}),
|
||||
pre_command: |ctx: ManifoldContext<'_>| Box::pin(async move {
|
||||
info!("Received command {} from {}", ctx.command().name, ctx.author().name);
|
||||
let config = &ctx.data().bot_config;
|
||||
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;
|
||||
let _ = ctx.data().bot_config.channels.log.say(ctx, format!("Received command {} from {}", ctx.command().name, ctx.author().name)).await;
|
||||
}),
|
||||
commands: commands::collect_commands(injected_commands),
|
||||
prefix_options: poise::PrefixFrameworkOptions {
|
||||
prefix: Some(prefix),
|
||||
prefix: Some(bot_config.prefix.clone()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
|
|
@ -124,13 +118,13 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
|
|||
apply_migrations(&mut db.get()?);
|
||||
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();
|
||||
|
||||
Ok(ManifoldData(Arc::new(ManifoldDataInner {
|
||||
bot_config: config,
|
||||
bot_config,
|
||||
database: db,
|
||||
responses,
|
||||
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),
|
||||
frogtip_fueltank: Mutex::new(FuelTank::with_cache())
|
||||
})))
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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(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,78 +1,57 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use dirs::home_dir;
|
||||
use std::io::{SeekFrom, Seek};
|
||||
use reqwest::Client;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::error::{ManifoldError, ManifoldResult};
|
||||
|
||||
const CACHE_VERSION: &'static str = "1";
|
||||
|
||||
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)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
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)]
|
||||
enum TankMode {
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TankMode {
|
||||
Cache,
|
||||
NoCache,
|
||||
}
|
||||
|
||||
impl FuelTank {
|
||||
pub fn with_cache() -> Self {
|
||||
pub fn new(source_uri: String, cache_name: Option<String>, api_key: Option<String>, cache_mode: TankMode) -> Self {
|
||||
Self {
|
||||
mode: TankMode::Cache,
|
||||
source_uri,
|
||||
cache_name,
|
||||
api_key,
|
||||
cache_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
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> {
|
||||
pub async fn next_or_fill<T: Debug, U: Debug>(&self) -> ManifoldResult<T>
|
||||
where T: DeserializeOwned,
|
||||
U: Serialize,
|
||||
U: DeserializeOwned, U: Pump<T>
|
||||
{
|
||||
debug!("Filling tank!");
|
||||
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));
|
||||
path.push(format!("manifold-{}-cache-{}.json", self.cache_name.clone().unwrap(), CACHE_VERSION));
|
||||
debug!("Cache path: {:?}", &path);
|
||||
fs::OpenOptions::new()
|
||||
.read(true)
|
||||
|
|
@ -87,29 +66,47 @@ impl FuelTank {
|
|||
|
||||
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);
|
||||
let mut tips = match json {
|
||||
let mut fuel = match json {
|
||||
Ok(contents) => {
|
||||
match contents.len() {
|
||||
0 => croak().await?,
|
||||
0 => self.emit().await?,
|
||||
_ => contents,
|
||||
}
|
||||
}
|
||||
_ => croak().await?,
|
||||
_ => self.emit().await?,
|
||||
};
|
||||
|
||||
debug!("Got tips: {:?}", &tips);
|
||||
let tip = tips.pop().ok_or(ManifoldError::from("FROG HAS NO TIPS FOR YOU. CROAK ERROR 12"))?;
|
||||
debug!("Got tips: {:?}", &fuel);
|
||||
let single = fuel.pump().ok_or(ManifoldError::from("CACHE ERROR"))?;
|
||||
|
||||
debug!("Got single tip: {:?}", &tip);
|
||||
debug!("Got single tip: {:?}", &single);
|
||||
cache
|
||||
.set_len(0)
|
||||
.and_then(|_| cache.seek(SeekFrom::Start(0)))?;
|
||||
|
||||
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");
|
||||
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,5 @@
|
|||
pub mod animalpics;
|
||||
pub mod fueltank;
|
||||
pub mod frogtip;
|
||||
pub mod user;
|
||||
pub mod weather;
|
||||
|
|
@ -1,11 +1,7 @@
|
|||
use crate::error::ManifoldResult;
|
||||
use crate::error::{ManifoldError, ManifoldResult};
|
||||
use reqwest::Client;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
|
||||
pub struct Weather {
|
||||
base_url: String,
|
||||
api_key: String,
|
||||
}
|
||||
use crate::models::fueltank::FuelTank;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CurrentConditionInformation {
|
||||
|
|
@ -145,22 +141,13 @@ pub struct WeatherForecastRequestResponse {
|
|||
pub forecast: ForecastDayWrapper,
|
||||
}
|
||||
|
||||
pub type Weather = FuelTank;
|
||||
|
||||
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> {
|
||||
let target_url: String;
|
||||
pub async fn get_weather_forecast(&self, target: String) -> ManifoldResult<WeatherForecastRequestResponse> {
|
||||
|
||||
if let Some(d) = days {
|
||||
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 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);
|
||||
|
||||
let client = Client::new();
|
||||
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);
|
||||
|
||||
|
||||
debug!("{:?}", decoded_result);
|
||||
debug!("{:?}", &decoded_result);
|
||||
|
||||
Ok(decoded_result)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue