Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SL travel planner command #12

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "joel-bot"
version = "1.0.2"
version = "1.0.5"
authors = [
"Fabian Eriksson <[email protected]>",
"Joakim Anell <[email protected]>",
Expand All @@ -18,8 +18,10 @@ path = "src/lib.rs"
reqwest = { version = "0.10.9", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_yaml = "0.8.14"
serde_json = "1.0"
rocket = { version = "0.4.6" }
rocket_contrib = { version = "0.4.6", default-features = false, features = ["json"] }
chrono = "0.4.19"
chrono-tz = "0.5.3"
clokwerk = "0.3.3"
rand = "0.7.3"
3 changes: 2 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ intro:
- "Hej hörni! :joel:"
- "Tjenis! Är allt väl? :joel:"
- "Yo, allt väl hoppas jag? :joel:"
about_me: "Mitt namn är joel-bot och jag har axlat @chikken's arbete nu när han inte längre finns ibland oss!\nJag kommer pliktskyldigt påminna er om att tidsrapportera sista arbetsdagen i månaden och kan även svara på frågor kring tidrapportering.\nNi kan läsa mig här: https://github.com/Pirayya/joel-bot"
about_me: "Mitt namn är joel-bot och jag har axlat @chikken's (Joel Wahlund) arbete nu när han inte längre finns ibland oss!\nJag kommer pliktskyldigt påminna er om att tidsrapportera sista arbetsdagen i månaden och kan även svara på frågor kring tidrapportering.\nNi kan läsa mig här: https://github.com/Pirayya/joel-bot"
features:
- "/joel - prova! Bara du som ser!"
- "/ta-mig-hem [från, till] - jag kollar på SL åt dig och ger dig tre resförslag, ex: `/ta-mig-hem t-centr fruängen`"
kazie marked this conversation as resolved.
Show resolved Hide resolved
- "tid - fråga mig om när ni ska tidsrapportera denna månaden"
- "pricing - hur mycket kostar jag, alltså vad skulle det kosta att köra en on-premise joel-bot?"
- "skribenter - mina skapare, _i bokstavsordning på efternamn_"
Expand Down
6 changes: 6 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pub mod routes;
mod sl;
mod slack;

pub use self::sl::*;
pub use self::slack::*;
206 changes: 206 additions & 0 deletions src/api/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
use std::collections::HashMap;
use std::error::Error;
use std::thread;
use std::time::Duration;

use chrono::Utc;
use rand::{Rng, thread_rng};
use reqwest::blocking::Client;
use rocket::FromForm;
use rocket::http::Status;
use rocket::post;
use rocket::request::LenientForm;
use rocket::State;
use rocket_contrib::json::Json;
use serde::Serialize;

use crate::api::{SlackBlockResponse, SlackErrorResponse, SLApi, SLApiKeys, SLStationInfo, SLStationInfoResponse};
use crate::events::{SlackEvents, SlackRequest};
use crate::last_day::get_last_workday;

#[post("/take-me-home", format = "application/x-www-form-urlencoded", data = "<request>")]
pub fn handle_trip_command(state: State<SLApiKeys>, request: LenientForm<SlackSlashMessage>) -> String {
let stations: Vec<&str> = request.text.split(' ').collect();
if stations.len() != 2 {
println!("Got too few stations");
return "Jag behöver två argument fattaru la? Ex: `/gg t-centr fruän`".to_string();
};

let from_name = stations[0].to_string();
let to_name = stations[1].to_string();

let trip_token = state.get_trip_token.clone();
let stations_token = state.get_stations_token.clone();
thread::spawn(move || {
let http_client = Client::new();

let from_result = SLApi::read_station(&http_client, &stations_token, &from_name, 1);
let to_result = SLApi::read_station(&http_client, &stations_token, &to_name, 1);

let from_station = match get_first_station(from_result) {
Some(f) => f,
None => {
println!("Could not find a station with name: {}", from_name);
send_json_response(&http_client, &request.response_url, &SlackErrorResponse::new(format!("Hittade ingen station med namnet och {}", &from_name)));
return;
}
};

let to_station = match get_first_station(to_result) {
Some(t) => t,
None => {
println!("Could not find a station with name: {}", to_name);
send_json_response(&http_client, &request.response_url, &SlackErrorResponse::new(format!("Hittade ingen station med namnet {}", &to_name)));
return;
}
};

let result = SLApi::list_trips(&http_client, &trip_token, &from_station.site_id, &to_station.site_id);

let result = match result {
Ok(trip_response) => {
SlackBlockResponse::create_trip_response(&from_name, &to_name, &trip_response)
}
Err(error) => {
println!("Error: {}", error);
send_json_response(&http_client, &request.response_url, &SlackErrorResponse::new(format!("Hittade ingen resa mellan {} och {}", &from_name, &to_name)));
return;
}
};

send_json_response(&http_client, &request.response_url, &result)
});

String::from("Låt mig se efter om det finns en resa hos SL åt dig!")
}

fn get_first_station(result: Result<SLStationInfoResponse, Box<dyn Error>>) -> Option<SLStationInfo> {
match result {
Ok(to) => {
let data = to.response_data.unwrap();
match data.get(0) {
Some(t) => Some(t.clone()),
None => {
// TODO: Send error
None
}
}
}
Err(_error) => {
// TODO: Send error
None
}
}
}

#[post("/slack-request", format = "application/json", data = "<request>")]
pub fn slack_request(state: State<SlackEvents>, request: Json<SlackRequest>) -> String {
state.handle_request(request.0)
}

// More information here: https://api.slack.com/interactivity/slash-commands
#[derive(FromForm)]
pub struct SlackSlashMessage {
// token: String, <-- We should save and validate this
// command: String, <-- can be used to check what command was used.
text: String,
// <-- Seems to exists even if it is empty
response_url: String,
}

#[post("/time-report", format = "application/x-www-form-urlencoded", data = "<request>")]
pub fn time_report(request: LenientForm<SlackSlashMessage>) -> String {
let response_url = request.response_url.clone();

let calculations = vec!["vänta", "beräknar", "processerar", "finurlar", "gnuggar halvledarna", "tömmer kvicksilver-depå"];

thread::spawn(move || {
let now = Utc::now();
let http_client = Client::new();
let mut map = HashMap::new();

match get_last_workday(&now) {
Ok(last_workday) => {
if last_workday == now.naive_utc().date() {
map.insert("text", format!("Okej, jag har kikat i kalendern och det är först *{}* som du behöver tidrapportera!", last_workday));

sleep_and_send_time_report_response(&http_client, &response_url, &map);

let mut rng = thread_rng();
for _ in 0..2 {
let pos = rng.gen_range(0, calculations.len() - 1);

map.insert("text", format!("... {}", calculations[pos]));

sleep_and_send_time_report_response(&http_client, &response_url, &map);
}

map.insert("text", String::from("... det är ju idag!"));

sleep_and_send_time_report_response(&http_client, &response_url, &map);
} else {
map.insert("text", format!("Nu har jag gjort diverse uppslag och scrape:at nätet och det är inte förrän *{}* som du behöver tidrapportera!", last_workday));

sleep_and_send_time_report_response(&http_client, &response_url, &map)
}
}
Err(error) => {
println!("failed to get last work day: {}", error);

map.insert("text", String::from("Misslyckades stenhårt..."));
sleep_and_send_time_report_response(&http_client, &response_url, &map)
}
};
});

format!("Ska ta en titt i kalendern...")
}

fn sleep_and_send_time_report_response(http_client: &Client, url: &String, map: &HashMap<&str, String>) {
// To "fool" the user that we are actually calculating something
thread::sleep(Duration::from_secs(2));

send_response(http_client, url, map)
}

fn send_response(http_client: &Client, url: &String, map: &HashMap<&str, String>) {
let resp = http_client.post(url.as_str())
.json(map)
.send();

match resp {
Ok(r) => {
if !r.status().is_success() {
println!("failed to send message, {}", r.status().as_str());
let result = r.text();
if result.is_ok() {
println!("{}", result.unwrap());
}
}
}
Err(err) => {
println!("got exception while sending message: {}", err)
}
}
}

fn send_json_response(http_client: &Client, url: &String, data: &impl Serialize) {
let resp = http_client.post(url.as_str())
.json(data)
.send();

match resp {
Ok(r) => {
if !r.status().is_success() {
println!("failed to send message, {}", r.status().as_str());
let result = r.text();
if result.is_ok() {
println!("{}", result.unwrap());
}
}
}
Err(err) => {
println!("got exception while sending message: {}", err)
}
}
}
Loading