V3 - config refactoring, animal pics

This commit is contained in:
Xyon 2023-08-30 16:53:24 +01:00
parent 4f9171d96a
commit c9f6f5ced0
Signed by: xyon
GPG Key ID: DD18155D6B18078D
14 changed files with 205 additions and 205 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "manifold" name = "manifold"
version = "2.0.0" version = "3.0.0"
authors = ["Lucy Bladen <admin@lbladen.uk>"] authors = ["Lucy Bladen <admin@lbladen.uk>"]
edition = "2021" edition = "2021"

View File

@ -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()]
} }

50
src/commands/animal.rs Normal file
View File

@ -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()]
}

View File

@ -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()]
}

View File

@ -1,5 +1,5 @@
mod admin; mod admin;
mod frog; mod animal;
mod convert; mod convert;
mod core; mod core;
mod weather; mod weather;
@ -9,7 +9,7 @@ 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(weather::commands()) .chain(weather::commands())
.chain(injected) .chain(injected)

View File

@ -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?;

View File

@ -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>()?))
} }
} }

View File

@ -3,7 +3,6 @@ use std::sync::atomic::AtomicBool;
use poise::{Event, FrameworkContext}; use poise::{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::{Context, Message};
use crate::ManifoldData; use crate::ManifoldData;
@ -40,10 +39,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 +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(()) 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<()> { 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() { if let Some(new) = new_message.as_ref() {
log_channel.send_message(ctx, |f| { log_channel.send_message(ctx, |f| {

View File

@ -18,7 +18,6 @@ use crate::config::ManifoldConfig;
use crate::error::{ManifoldError, ManifoldResult}; use crate::error::{ManifoldError, ManifoldResult};
use crate::events::Handler; use crate::events::Handler;
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;
@ -67,7 +66,6 @@ pub struct ManifoldDataInner {
responses: Responses, responses: Responses,
user_info: Mutex<ManifoldUserInfo>, user_info: Mutex<ManifoldUserInfo>,
version_string: String, 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>;
@ -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>> { 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 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",
@ -103,13 +99,11 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
}), }),
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()
@ -124,13 +118,13 @@ pub async fn prepare_client(arguments: ArgMatches, intents: GatewayIntents, inje
apply_migrations(&mut db.get()?); apply_migrations(&mut db.get()?);
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())
}))) })))
}) })
}); });

26
src/models/animalpics.rs Normal file
View File

@ -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()
}
}

29
src/models/frogtip.rs Normal file
View File

@ -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()
}
}

View File

@ -1,78 +1,57 @@
use std::fmt::{Display, Formatter}; use std::fmt::Debug;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use dirs::home_dir; 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::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)]
enum TankMode { pub enum TankMode {
Cache, Cache,
NoCache,
} }
impl FuelTank { 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 { Self {
mode: TankMode::Cache, source_uri,
cache_name,
api_key,
cache_mode,
} }
} }
}
#[derive(Clone, Serialize, Deserialize, Debug)] pub async fn next_or_fill<T: Debug, U: Debug>(&self) -> ManifoldResult<T>
pub struct Tip { where T: DeserializeOwned,
pub number: i32, U: Serialize,
pub tip: String, U: DeserializeOwned, U: Pump<T>
} {
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(); let mut path = PathBuf::new();
path.push(home_dir().ok_or(ManifoldError::from("No home dir"))?); path.push(home_dir().ok_or(ManifoldError::from("No home dir"))?);
path.push(".local"); 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); debug!("Cache path: {:?}", &path);
fs::OpenOptions::new() fs::OpenOptions::new()
.read(true) .read(true)
@ -87,29 +66,47 @@ 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 { let mut fuel = 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?,
}; };
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);
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");
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)
} }
} }

View File

@ -1,3 +1,5 @@
pub mod animalpics;
pub mod fueltank; pub mod fueltank;
pub mod frogtip;
pub mod user; pub mod user;
pub mod weather; pub mod weather;

View File

@ -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)
} }