Further implementation

This commit is contained in:
Rob Watson 2020-10-17 22:04:21 +02:00
parent 68200ee28a
commit bc2e1d0f40
6 changed files with 201 additions and 842 deletions

798
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,9 +6,9 @@ edition = "2018"
[dependencies]
dotenv = "0.15.0"
futures-util = "0.3.6"
reqwest = { version = "0.10.8", features = ["json"] }
rocket = { git = "https://github.com/SergioBenitez/Rocket", default-features = false }
rocket_contrib = { git = "https://github.com/SergioBenitez/Rocket", default-features = false, features = ["handlebars_templates", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
slack_api = "0.23.1"
tokio = { version = "0.2", features = ["full"] }

22
src/config.rs Normal file
View File

@ -0,0 +1,22 @@
use serde::Deserialize;
use std::error::Error;
use std::fs;
#[derive(Debug, Deserialize)]
pub struct ConfigUser {
pub name: String,
pub location: String,
}
#[derive(Debug, Deserialize)]
pub struct Config {
pub config_users: Vec<ConfigUser>,
}
const CONFIG_FILENAME: &str = "config.json";
pub fn get_config() -> Result<Config, Box<dyn Error>> {
let data = fs::read_to_string(CONFIG_FILENAME)?;
serde_json::from_str(&data).map_err(|e| e.into())
}

View File

@ -1,39 +1,49 @@
#[macro_use]
extern crate rocket;
use dotenv::dotenv;
use rocket_contrib::templates::Template;
use serde::Serialize;
mod config;
mod slack;
mod weather;
#[derive(Serialize)]
struct Context {}
use config::ConfigUser;
use dotenv::dotenv;
use serde::Serialize;
use slack::SlackUser;
use tokio::prelude::*;
#[get("/weather/<loc>")]
async fn get_weather(loc: String) -> Result<String, String> {
let sum = weather::get_summary(&loc).await?;
Ok(format!("The weather in {} is: {:?}", loc, sum))
}
#[get("/slack/<msg>")]
async fn set_status(msg: String) -> Result<String, String> {
slack::set_status(&msg).await?;
Ok("woohoo".to_owned())
}
#[get("/")]
async fn index() -> Template {
let ctx = Context {};
Template::render("index", &ctx)
}
#[rocket::launch]
async fn rocket() -> rocket::Rocket {
#[tokio::main]
async fn main() {
dotenv().ok();
rocket::ignite()
.attach(Template::fairing())
.mount("/", routes![index, get_weather, set_status])
let config = config::get_config().unwrap();
println!("Got config: {:?}", config);
let slack_users = slack::list_users().await.unwrap();
println!("Got {} users: {:?}", slack_users.len(), slack_users);
for slack_user in slack_users {
let config_user = config
.config_users
.iter()
.find(|config_user| config_user.name == slack_user.name);
if let Some(config_user) = config_user {
match set_user_status(&config_user, &slack_user).await {
Ok(_) => (),
Err(e) => println!(
"Error calling set_user_status for user {}: {}",
config_user.name, e
),
}
}
}
}
async fn set_user_status(config_user: &ConfigUser, slack_user: &SlackUser) -> Result<(), String> {
let sum = weather::get_summary(&config_user.location).await?;
let status = format!("{} {}\u{00b0}C", sum.name, sum.temp_max);
let emoji = format!(":{}:", sum.emoji);
println!(
"Set status for user {} to: {} {:?}",
&slack_user.id, &emoji, &status
);
Ok(slack::set_status(&slack_user.id, &status, &emoji, 0).await?)
}

View File

@ -1,4 +1,4 @@
use crate::rocket::futures::TryFutureExt;
use futures_util::future::TryFutureExt;
use serde::Serialize;
use serde_json::json;
use slack_api;
@ -11,17 +11,41 @@ struct SlackProfile<'a> {
status_expiration: i32,
}
pub async fn set_status(msg: &str) -> Result<(), String> {
#[derive(Debug, Serialize)]
pub struct SlackUser {
pub id: String,
pub name: String,
pub tz_label: String,
pub tz_offset: i32,
}
impl From<slack_api::User> for SlackUser {
fn from(user: slack_api::User) -> Self {
SlackUser {
id: user.id.unwrap_or_default(),
name: user.name.unwrap_or_default(),
tz_label: user.tz_label.unwrap_or_default(),
tz_offset: user.tz_offset.unwrap_or_default() as i32,
}
}
}
pub async fn set_status(
user_id: &str,
status_text: &str,
status_emoji: &str,
status_expiration: i32,
) -> Result<(), String> {
let client = slack_api::default_client().map_err(|e| e.to_string())?;
let api_key = env::var("SLACK_OAUTH_ACCESS_TOKEN").expect("SLACK_OAUTH_ACCESS_TOKEN");
let profile = SlackProfile {
status_text: msg,
status_emoji: ":mountain:",
status_expiration: 0,
status_text,
status_emoji,
status_expiration,
};
let encoded_profile = json!(profile).to_string();
let set_req = slack_api::users_profile::SetRequest {
user: None,
user: Some(&user_id),
profile: Some(&encoded_profile),
name: None,
value: None,
@ -34,16 +58,24 @@ pub async fn set_status(msg: &str) -> Result<(), String> {
Ok(())
}
pub async fn update_all() -> Result<(), String> {
pub async fn list_users() -> Result<Vec<SlackUser>, String> {
let client = slack_api::default_client().map_err(|e| e.to_string())?;
let api_key = env::var("SLACK_OAUTH_ACCESS_TOKEN").expect("SLACK_OAUTH_ACCESS_TOKEN");
let list_req = slack_api::users::ListRequest::default();
let resp = slack_api::users::list(&client, &api_key, &list_req)
.map_err(|e| e.to_string())
.await?;
let members = resp.members.unwrap();
println!("Got vec with {} members", members.len());
let users = resp
.members
.unwrap_or_default()
.into_iter()
.map(|m| {
println!("Member: {:?}", m);
m.into()
})
.collect::<Vec<SlackUser>>();
Ok(())
Ok(users)
}

View File

@ -1,4 +1,4 @@
use crate::rocket::futures::TryFutureExt;
use futures_util::future::TryFutureExt;
use serde::Deserialize;
use std::env;
@ -11,6 +11,8 @@ struct APIResponseWeather {
#[derive(Debug, Deserialize)]
struct APIResponseMain {
temp: f32,
temp_min: f32,
temp_max: f32,
feels_like: f32,
humidity: u32,
pressure: u32,
@ -23,27 +25,77 @@ struct APIResponse {
}
#[derive(Debug)]
pub enum WeatherSummary {
Clear(i32),
Drizzle(i32),
Rain(i32),
Snow(i32),
Cloudy(i32),
Thunderstorm(i32),
Other(i32, String),
pub struct WeatherSummary {
pub name: String,
pub emoji: String,
pub temp: i32,
pub temp_min: i32,
pub temp_max: i32,
}
impl From<APIResponse> for WeatherSummary {
fn from(resp: APIResponse) -> Self {
let temp = resp.main.temp as i32;
let temp_min = resp.main.temp_min as i32;
let temp_max = resp.main.temp_max as i32;
match resp.weather[0].main.as_ref() {
"Thunderstorm" => Self::Thunderstorm(temp),
"Drizzle" => Self::Drizzle(temp),
"Rain" => Self::Rain(temp),
"Snow" => Self::Snow(temp),
"Clear" => Self::Clear(temp),
"Clouds" => Self::Cloudy(temp),
_ => Self::Other(temp, resp.weather[0].description.clone()),
"Thunderstorm" => Self {
name: "Thunderstorm".to_string(),
emoji: "thunder_cloud_and_rain".to_string(),
temp,
temp_min,
temp_max,
},
"Clouds" => Self {
name: "Cloudy".to_string(),
emoji: "cloud".to_string(),
temp,
temp_min,
temp_max,
},
"Snow" => Self {
name: "Snow".to_string(),
emoji: "snow_cloud".to_string(),
temp,
temp_min,
temp_max,
},
"Rain" => Self {
name: "Rain".to_string(),
emoji: "rain_cloud".to_string(),
temp,
temp_min,
temp_max,
},
"Drizzle" => Self {
name: "Drizzle".to_string(),
emoji: "rain_cloud".to_string(),
temp,
temp_min,
temp_max,
},
"Clear" => Self {
name: "Clear".to_string(),
emoji: "sunny".to_string(),
temp,
temp_min,
temp_max,
},
"Atmosphere" => Self {
name: "Atmospheric".to_string(),
emoji: "foggy".to_string(),
temp,
temp_min,
temp_max,
},
_ => Self {
name: "Unknown".to_string(),
emoji: "cloud".to_string(),
temp,
temp_min,
temp_max,
},
}
}
}
@ -55,9 +107,12 @@ pub async fn get_summary(loc: &str) -> Result<WeatherSummary, String> {
env::var("OPEN_WEATHERMAP_API_KEY").expect("could not load OPEN_WEATHERMAP_API_KEY")
);
let body = reqwest::get(&url)
.map_err(|e| e.to_string())
.await?
let resp = reqwest::get(&url).map_err(|e| e.to_string()).await?;
if !resp.status().is_success() {
return Err(format!("Weather API response: {}", resp.status()).into());
}
let body = resp
.json::<APIResponse>()
.map_err(|e| e.to_string())
.await?;