Further implementation
This commit is contained in:
parent
68200ee28a
commit
bc2e1d0f40
File diff suppressed because it is too large
Load Diff
|
@ -6,9 +6,9 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
|
futures-util = "0.3.6"
|
||||||
reqwest = { version = "0.10.8", features = ["json"] }
|
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 = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
slack_api = "0.23.1"
|
slack_api = "0.23.1"
|
||||||
|
tokio = { version = "0.2", features = ["full"] }
|
||||||
|
|
|
@ -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())
|
||||||
|
}
|
74
src/main.rs
74
src/main.rs
|
@ -1,39 +1,49 @@
|
||||||
#[macro_use]
|
mod config;
|
||||||
extern crate rocket;
|
|
||||||
|
|
||||||
use dotenv::dotenv;
|
|
||||||
use rocket_contrib::templates::Template;
|
|
||||||
use serde::Serialize;
|
|
||||||
|
|
||||||
mod slack;
|
mod slack;
|
||||||
mod weather;
|
mod weather;
|
||||||
|
|
||||||
#[derive(Serialize)]
|
use config::ConfigUser;
|
||||||
struct Context {}
|
use dotenv::dotenv;
|
||||||
|
use serde::Serialize;
|
||||||
|
use slack::SlackUser;
|
||||||
|
use tokio::prelude::*;
|
||||||
|
|
||||||
#[get("/weather/<loc>")]
|
#[tokio::main]
|
||||||
async fn get_weather(loc: String) -> Result<String, String> {
|
async fn main() {
|
||||||
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 {
|
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
rocket::ignite()
|
let config = config::get_config().unwrap();
|
||||||
.attach(Template::fairing())
|
println!("Got config: {:?}", config);
|
||||||
.mount("/", routes![index, get_weather, set_status])
|
|
||||||
|
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?)
|
||||||
}
|
}
|
||||||
|
|
52
src/slack.rs
52
src/slack.rs
|
@ -1,4 +1,4 @@
|
||||||
use crate::rocket::futures::TryFutureExt;
|
use futures_util::future::TryFutureExt;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use slack_api;
|
use slack_api;
|
||||||
|
@ -11,17 +11,41 @@ struct SlackProfile<'a> {
|
||||||
status_expiration: i32,
|
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 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 api_key = env::var("SLACK_OAUTH_ACCESS_TOKEN").expect("SLACK_OAUTH_ACCESS_TOKEN");
|
||||||
let profile = SlackProfile {
|
let profile = SlackProfile {
|
||||||
status_text: msg,
|
status_text,
|
||||||
status_emoji: ":mountain:",
|
status_emoji,
|
||||||
status_expiration: 0,
|
status_expiration,
|
||||||
};
|
};
|
||||||
let encoded_profile = json!(profile).to_string();
|
let encoded_profile = json!(profile).to_string();
|
||||||
let set_req = slack_api::users_profile::SetRequest {
|
let set_req = slack_api::users_profile::SetRequest {
|
||||||
user: None,
|
user: Some(&user_id),
|
||||||
profile: Some(&encoded_profile),
|
profile: Some(&encoded_profile),
|
||||||
name: None,
|
name: None,
|
||||||
value: None,
|
value: None,
|
||||||
|
@ -34,16 +58,24 @@ pub async fn set_status(msg: &str) -> Result<(), String> {
|
||||||
Ok(())
|
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 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 api_key = env::var("SLACK_OAUTH_ACCESS_TOKEN").expect("SLACK_OAUTH_ACCESS_TOKEN");
|
||||||
let list_req = slack_api::users::ListRequest::default();
|
let list_req = slack_api::users::ListRequest::default();
|
||||||
|
|
||||||
let resp = slack_api::users::list(&client, &api_key, &list_req)
|
let resp = slack_api::users::list(&client, &api_key, &list_req)
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let members = resp.members.unwrap();
|
let users = resp
|
||||||
println!("Got vec with {} members", members.len());
|
.members
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.map(|m| {
|
||||||
|
println!("Member: {:?}", m);
|
||||||
|
m.into()
|
||||||
|
})
|
||||||
|
.collect::<Vec<SlackUser>>();
|
||||||
|
|
||||||
Ok(())
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::rocket::futures::TryFutureExt;
|
use futures_util::future::TryFutureExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
|
@ -11,6 +11,8 @@ struct APIResponseWeather {
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct APIResponseMain {
|
struct APIResponseMain {
|
||||||
temp: f32,
|
temp: f32,
|
||||||
|
temp_min: f32,
|
||||||
|
temp_max: f32,
|
||||||
feels_like: f32,
|
feels_like: f32,
|
||||||
humidity: u32,
|
humidity: u32,
|
||||||
pressure: u32,
|
pressure: u32,
|
||||||
|
@ -23,27 +25,77 @@ struct APIResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum WeatherSummary {
|
pub struct WeatherSummary {
|
||||||
Clear(i32),
|
pub name: String,
|
||||||
Drizzle(i32),
|
pub emoji: String,
|
||||||
Rain(i32),
|
pub temp: i32,
|
||||||
Snow(i32),
|
pub temp_min: i32,
|
||||||
Cloudy(i32),
|
pub temp_max: i32,
|
||||||
Thunderstorm(i32),
|
|
||||||
Other(i32, String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<APIResponse> for WeatherSummary {
|
impl From<APIResponse> for WeatherSummary {
|
||||||
fn from(resp: APIResponse) -> Self {
|
fn from(resp: APIResponse) -> Self {
|
||||||
let temp = resp.main.temp as i32;
|
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() {
|
match resp.weather[0].main.as_ref() {
|
||||||
"Thunderstorm" => Self::Thunderstorm(temp),
|
"Thunderstorm" => Self {
|
||||||
"Drizzle" => Self::Drizzle(temp),
|
name: "Thunderstorm".to_string(),
|
||||||
"Rain" => Self::Rain(temp),
|
emoji: "thunder_cloud_and_rain".to_string(),
|
||||||
"Snow" => Self::Snow(temp),
|
temp,
|
||||||
"Clear" => Self::Clear(temp),
|
temp_min,
|
||||||
"Clouds" => Self::Cloudy(temp),
|
temp_max,
|
||||||
_ => Self::Other(temp, resp.weather[0].description.clone()),
|
},
|
||||||
|
"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")
|
env::var("OPEN_WEATHERMAP_API_KEY").expect("could not load OPEN_WEATHERMAP_API_KEY")
|
||||||
);
|
);
|
||||||
|
|
||||||
let body = reqwest::get(&url)
|
let resp = reqwest::get(&url).map_err(|e| e.to_string()).await?;
|
||||||
.map_err(|e| e.to_string())
|
if !resp.status().is_success() {
|
||||||
.await?
|
return Err(format!("Weather API response: {}", resp.status()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = resp
|
||||||
.json::<APIResponse>()
|
.json::<APIResponse>()
|
||||||
.map_err(|e| e.to_string())
|
.map_err(|e| e.to_string())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
Loading…
Reference in New Issue