Skip to content

Instantly share code, notes, and snippets.

@rschulman
Created January 25, 2018 13:39
Show Gist options
  • Save rschulman/1ffee4827d993fae94ec7d889afaa464 to your computer and use it in GitHub Desktop.
Save rschulman/1ffee4827d993fae94ec7d889afaa464 to your computer and use it in GitHub Desktop.
#![feature(conservative_impl_trait)]
#![feature(generators)]
#![feature(proc_macro)]
#![feature(try_from)]
#![recursion_limit = "1024"]
#[macro_use]
extern crate diesel;
//#[macro_use]
//extern crate diesel_codegen;
extern crate chrono;
#[macro_use]
extern crate diesel_infer_schema;
#[macro_use]
extern crate nom;
//extern crate futures;
extern crate futures_await as futures;
extern crate hyper;
extern crate ruma_client;
extern crate ruma_events;
extern crate ruma_identifiers;
extern crate tokio_core;
extern crate url;
use std::cmp::max;
use std::io;
use std::convert::TryFrom;
use diesel::prelude::*;
use diesel::sqlite::SqliteConnection;
use chrono::NaiveDateTime;
use futures::prelude::*;
use ruma_client::api::r0;
use ruma_client::Client;
use ruma_events::EventType;
use ruma_events::collections::all::RoomEvent;
use ruma_events::room::message::{MessageEvent, MessageEventContent, MessageType,
TextMessageEventContent};
use ruma_identifiers::{RoomAliasId, RoomId};
use tokio_core::reactor::{Core as TokioCore, Handle as TokioHandle};
use url::Url;
use hyper::client::Connect;
mod models;
mod schema;
mod parsers;
use self::models::{NewTodo, Todo};
// from https://stackoverflow.com/a/43992218/1592377
#[macro_export]
macro_rules! clone {
(@param _) => ( _ );
(@param $x:ident) => ( $x );
($($n:ident),+ => move || $body:expr) => (
{
$( let $n = $n.clone(); )+
move || $body
}
);
($($n:ident),+ => move |$($p:tt),+| $body:expr) => (
{
$( let $n = $n.clone(); )+
move |$(clone!(@param $p),)+| $body
}
);
}
fn db_connect() -> SqliteConnection {
let db_url = "/home/ross/.config/northship/northship.db";
SqliteConnection::establish(&db_url).expect("Failure connecting to database.")
}
struct Northship {
database: SqliteConnection,
mapping: Vec<i32>,
}
/*fn send_matrix_message<C: Connect>
(client: ruma_client::Client<C>,
message: String,
room: RoomId)
-> impl Future<Item = (), Error = ruma_client::Error> + 'static {
}*/
impl Northship {
fn matrix_loop(
&self,
tokio_handle: &TokioHandle,
homeserver_url: Url,
username: String,
password: String,
) -> impl Future<Item = (), Error = ruma_client::Error> + 'static {
let client = ruma_client::Client::https(tokio_handle, homeserver_url, None).unwrap();
client
.log_in(username, password)
.and_then(clone!(client => move |self| {
client.sync(None, None, true).skip(1).for_each(|res| {
for (room_id, room) in res.rooms.join {
for event in room.timeline.events {
if let RoomEvent::RoomMessage(MessageEvent {
content: MessageEventContent::Text(
TextMessageEventContent {
body: msg_body,
..
}
),
user_id,
..
}) = event {
println!("{:?} in {:?}: {}", user_id, room_id, msg_body);
let input_parsed = parsers::command(&msg_body);
match input_parsed {
Some(result) => {
match result {
parsers::Command::Todo(todo) => {
match self.new_todo(todo.body,
todo.deadline,
todo.scheduled,
None,
room_id.to_string()) {
Ok(()) => {
let _ = r0::send::send_message_event::call(client.clone(),
r0::send::send_message_event::Request {
room_id: room_id,
event_type: EventType::RoomMessage,
txn_id: "1".to_owned(), // TODO Probably problematic
data:
MessageEventContent::Text(TextMessageEventContent {
body: "New TODO added".to_owned(),
msgtype: MessageType::Text,
}),
});
},
Err(_) => {
let _ = r0::send::send_message_event::call(client.clone(),
r0::send::send_message_event::Request {
room_id: room_id,
event_type: EventType::RoomMessage,
txn_id: "1".to_owned(), // TODO Probably problematic
data:
MessageEventContent::Text(TextMessageEventContent {
body: "Error adding TODO to datbaase.".to_owned(),
msgtype: MessageType::Text,
}),
});
},
}
},
parsers::Command::Agenda => {
let _ = r0::send::send_message_event::call(client.clone(),
r0::send::send_message_event::Request {
room_id: room_id,
event_type: EventType::RoomMessage,
txn_id: "1".to_owned(), // TODO Probably problematic
data:
MessageEventContent::Text(TextMessageEventContent {
body: self.format_todos().unwrap().to_owned(),
msgtype: MessageType::Text,
}),
});
},
}
},
None => {
let _ = r0::send::send_message_event::call(client.clone(),
r0::send::send_message_event::Request {
room_id: room_id,
event_type: EventType::RoomMessage,
txn_id: "1".to_owned(), // TODO Probably problematic
data:
MessageEventContent::Text(TextMessageEventContent {
body: "Sorry, I didn't catch that. Try again?".to_owned(),
msgtype: MessageType::Text,
}),
});
},
}
}
}
}
Ok(())
})
}))
}
fn format_todos(&self) -> Result<String, diesel::result::Error> {
use schema::todos::dsl::*;
let results = todos
.filter(room.eq("roomids"))
.limit(20)
.load::<Todo>(&self.database)
.expect("Error loading Todos");
let mut maxes = vec![0, "Deadline".len(), "Scheduled".len()];
for todo in results.iter() {
maxes[0] = max(todo.content.len(), maxes[0]);
match todo.deadline {
Some(ref item) => {
maxes[1] = max(item.format("%Y-%m-%d").to_string().len(), maxes[1])
}
None => {}
}
match todo.scheduled {
Some(ref item) => {
maxes[2] = max(item.format("%Y-%m-%d %H:%M:%S").to_string().len(), maxes[2])
}
None => {}
}
}
let mut formatted_results: String = String::new();
formatted_results.push_str(&format!(
"| #|{todo_title: ^widtha$}|{dead_title: ^widthb$}|{sched_title: \
^widthc$}|{eff_title:^4}|\n|{rule:-<widthd$}|\n",
todo_title = "TODOs",
widtha = maxes[0] + 2,
dead_title = "Deadline",
widthb = maxes[1] + 2,
sched_title = "Scheduled",
widthc = maxes[2] + 2,
eff_title = "Effort",
rule = "",
widthd = 12 + 6 + maxes[0] + maxes[1] + maxes[2]
));
for (index, todo) in results.iter().enumerate() {
formatted_results.push_str(&format!("|{number:>2}|{the_todo: ^widtha$}|{dead: ^widthb$}|{sched: ^widthc$}|{eff:>6}|\n|{rule:-<widthd$}|\n",
number = &(index + 1).to_string(),
the_todo = &todo.content,
widtha = maxes[0] + 2,
dead = match todo.deadline {
Some(ref duedate) => format!("{}", duedate.format("%Y-%m-%d")),
None => "".to_string(),
},
widthb = maxes[1] + 2,
sched = match todo.scheduled {
Some(ref scheddate) =>format!("{}", scheddate.format("%Y-%m-%d %H:%M:%S")),
None => "".to_string(),
},
widthc = maxes[2] + 2,
eff = match &todo.effort {
&Some(ref minutes) => minutes.to_string(),
&None => " ".to_string(),
},
rule = "",
widthd = 12 + 6 + maxes[0] + maxes[1] + maxes[2]));
}
Ok(formatted_results)
}
fn new_todo(
&self,
content: String,
deadline: Option<NaiveDateTime>,
scheduled: Option<NaiveDateTime>,
effort: Option<i32>,
room: String,
) -> Result<(), diesel::result::Error> {
use schema::todos;
let obligation = NewTodo {
content: &content,
deadline: deadline,
scheduled: scheduled,
effort: effort,
room: &room,
};
diesel::insert_into(todos::table)
.values(&obligation)
.execute(&self.database)
.expect("Error saving new todo");
Ok(())
}
}
fn main() {
use schema::todos::dsl::*;
let dbc = db_connect();
let host = Northship {
database: dbc,
mapping: vec![0, 0],
};
let username = "northship".to_owned();
let password = ")'sCVcJJ~1sL|@f,1zYxA'f2j".to_owned();
let mut core = TokioCore::new().unwrap();
let handle = core.handle();
let server = Url::parse("https://matrix.westwork.org/").unwrap();
core.run(host.matrix_loop(&handle, server, username, password))
.unwrap();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment