diff options
| author | Nathan Perry <avaglir@gmail.com> | 2019-03-08 16:57:27 -0500 |
|---|---|---|
| committer | Nathan Perry <avaglir@gmail.com> | 2019-03-08 16:57:27 -0500 |
| commit | 86025df1f6d814c98a14211ceb4da6cf6de915c7 (patch) | |
| tree | c615b6634eea05498d772f68aec2d52fa3ca4f01 | |
| parent | 24a7cb3c8eb0517b69c145170765c891a82ccc76 (diff) | |
first pass with oauth
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Cargo.lock | 75 | ||||
| -rw-r--r-- | Cargo.toml | 6 | ||||
| -rw-r--r-- | migrations/2019-03-08-073831_store-token/down.sql | 2 | ||||
| -rw-r--r-- | migrations/2019-03-08-073831_store-token/up.sql | 10 | ||||
| -rw-r--r-- | src/db/models.rs | 43 | ||||
| -rw-r--r-- | src/db/schema.rs | 11 | ||||
| -rw-r--r-- | src/game.rs | 330 | ||||
| -rw-r--r-- | src/main.rs | 23 |
9 files changed, 499 insertions, 3 deletions
@@ -1,6 +1,6 @@ - /target/ **/*.rs.bk .env .vscode *.log +user_id_mapping.json @@ -307,6 +307,35 @@ dependencies = [ ] [[package]] +name = "curl" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "curl-sys 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.42 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "curl-sys" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.42 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "diesel" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -724,6 +753,17 @@ dependencies = [ ] [[package]] +name = "libz-sys" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.30 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "lock_api" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -971,6 +1011,19 @@ dependencies = [ ] [[package]] +name = "oauth2" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "curl 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "openssl" version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1510,6 +1563,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "serde" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde_derive" @@ -1596,6 +1652,17 @@ version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "socket2" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.49 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "sodiumoxide" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1759,13 +1826,16 @@ dependencies = [ "either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "fern 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "nom 4.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "oauth2 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "reqwest 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "serenity 0.5.11 (git+https://github.com/mammothbane/serenity)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2156,6 +2226,8 @@ dependencies = [ "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" "checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e" +"checksum curl 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)" = "c7c9d851c825e0c033979d4516c9173bc19a78a96eb4d6ae51d4045440eafa16" +"checksum curl-sys 0.4.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ca79238a79fb294be6173b4057c95b22a718c94c4e38475d5faa82b8383f3502" "checksum diesel 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a2469cbcf1dfb9446e491cac4c493c2554133f87f7d041e892ac82e5cd36e863" "checksum diesel_derives 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "62a27666098617d52c487a41f70de23d44a1dc1f3aa5877ceba2790fb1f1cab4" "checksum dotenv 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d0a1279c96732bc6800ce6337b6a614697b0e74ae058dc03c62ebeb78b4d86" @@ -2202,6 +2274,7 @@ dependencies = [ "checksum libflate 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "54d1ddf9c52870243c5689d7638d888331c1116aa5b398f3ba1acfa7d8758ca1" "checksum libloading 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3ad660d7cb8c5822cd83d10897b0f1f1526792737a179e73896152f85b88c2" "checksum libsodium-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01839d6a151535905648d69dbbbf9c3f8f104b7890469508d504f4cd48d64643" +"checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe" "checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" @@ -2229,6 +2302,7 @@ dependencies = [ "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum num_cpus 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" +"checksum oauth2 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18c3c3e438a0e6cbbbfc72969a5ba24e8cae74c244642ef97b0bfab2823b2860" "checksum openssl 0.10.19 (registry+https://github.com/rust-lang/crates.io-index)" = "84321fb9004c3bce5611188a644d6171f895fa2889d155927d528782edb21c5d" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.42 (registry+https://github.com/rust-lang/crates.io-index)" = "cb534d752bf98cf363b473950659ac2546517f9c6be9723771614ab3f03bbc9e" @@ -2301,6 +2375,7 @@ dependencies = [ "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" +"checksum socket2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c4d11a52082057d87cb5caa31ad812f4504b97ab44732cd8359df2e9ff9f48e7" "checksum sodiumoxide 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "71c0682b4406fa25d621b19d2e70b5f6c8627e39b4b7ce0e24b2ef05d0fbe1ca" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10102ac8d55e35db2b3fafc26f81ba8647da2e15879ab686a67e6d19af2685e8" @@ -4,7 +4,8 @@ version = "0.1.6" authors = ["Nathan Perry <avaglir@gmail.com>"] [features] -default = ["diesel"] +default = ["diesel", "games"] +games = ["oauth2"] [dependencies] lazy_static = "~1.3" @@ -25,10 +26,13 @@ reqwest = "^0.9" sha1 = { version = "^0.6", features = ["std"] } regex = "~1.1" itertools = "^0.8" +serde = { version = "~1.0", features = ["derive"] } serde_json = "~1.0" timeago = "^0.1" nom = { version = "~4.2", features = ["verbose-errors"] } statrs = "^0.10" +fnv = "~1.0" +oauth2 = { version = "~1.3", optional = true } [dependencies.serenity] default-features = false diff --git a/migrations/2019-03-08-073831_store-token/down.sql b/migrations/2019-03-08-073831_store-token/down.sql new file mode 100644 index 0000000..c2bacf0 --- /dev/null +++ b/migrations/2019-03-08-073831_store-token/down.sql @@ -0,0 +1,2 @@ +DROP INDEX oauth_token_exp_index; +DROP TABLE google_oauth_tokens; diff --git a/migrations/2019-03-08-073831_store-token/up.sql b/migrations/2019-03-08-073831_store-token/up.sql new file mode 100644 index 0000000..94f826b --- /dev/null +++ b/migrations/2019-03-08-073831_store-token/up.sql @@ -0,0 +1,10 @@ +CREATE TABLE google_oauth_tokens ( + id SERIAL PRIMARY KEY, + token VARCHAR NOT NULL, + refresh_token VARCHAR NOT NULL, + expiration TIMESTAMP NOT NULL, + created TIMESTAMP NOT NULL DEFAULT current_timestamp +); + +CREATE INDEX oauth_token_exp_index ON google_oauth_tokens (expiration); +CREATE INDEX oauth_token_created_index ON google_oauth_tokens (created); diff --git a/src/db/models.rs b/src/db/models.rs index 66e161a..030daba 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -284,3 +284,46 @@ impl InvocationRecord { .map_err(Error::from) } } + +#[derive(Queryable, Identifiable, PartialEq, Debug)] +#[table_name="google_oauth_tokens"] +pub struct GoogleOAuthToken { + pub id: i32, + pub token: String, + pub refresh_token: String, + pub expiration: NaiveDateTime, + pub created: NaiveDateTime, +} + +impl GoogleOAuthToken { + pub fn create(conn: &PgConnection, token: String, refresh_token: String, expiration: NaiveDateTime) -> Result<Self> { + ::diesel::insert_into(google_oauth_tokens::table) + .values(&NewGoogleOAuthToken { + token, + refresh_token, + expiration, + }) + .get_result::<GoogleOAuthToken>(conn) + .map_err(Error::from) + } + + pub fn latest(conn: &PgConnection) -> Result<Self> { + use chrono; + + let now = chrono::Utc::now().naive_utc(); + + google_oauth_tokens::table + .filter(google_oauth_tokens::expiration.gt(now)) + .order(google_oauth_tokens::created.desc()) + .first(conn) + .map_err(Error::from) + } +} + +#[derive(Insertable, PartialEq, Debug)] +#[table_name="google_oauth_tokens"] +pub struct NewGoogleOAuthToken { + pub token: String, + pub refresh_token: String, + pub expiration: NaiveDateTime, +} diff --git a/src/db/schema.rs b/src/db/schema.rs index 01ec1d0..9a67cec 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -17,6 +17,16 @@ table! { } table! { + google_oauth_tokens (id) { + id -> Int4, + token -> Varchar, + refresh_token -> Varchar, + expiration -> Timestamp, + created -> Timestamp, + } +} + +table! { images (id) { id -> Int4, data -> Bytea, @@ -78,6 +88,7 @@ joinable!(invocation_records -> memes (meme_id)); allow_tables_to_appear_in_same_query!( audio, audit_records, + google_oauth_tokens, images, invocation_records, memes, diff --git a/src/game.rs b/src/game.rs new file mode 100644 index 0000000..200ef42 --- /dev/null +++ b/src/game.rs @@ -0,0 +1,330 @@ +use failure::err_msg; +use oauth2::Config; +use serenity::{ + framework::standard::{ + Args, + StandardFramework, + }, + model::{ + channel::Message, + id::UserId, + }, + prelude::*, +}; + +use crate::{ + commands::send, + must_env_lookup, + Result, + VOICE_CHANNEL_ID, +}; + +lazy_static! { + static ref SHEETS_CLIENT_ID: String = must_env_lookup("SHEETS_CLIENT_ID"); + static ref SHEETS_SECRET: String = must_env_lookup("SHEETS_CLIENT_SECRET"); + static ref SPREADSHEET_ID: String = must_env_lookup("SPREADSHEET_ID"); +} + +#[cfg(debug_assertions)] const REDIRECT_URL: &'static str = "http://localhost:8080"; +#[cfg(not(debug_assertions))] const REDIRECT_URL: &'static str = "https://somali-derp.com/thulani_redirect"; + +pub fn register(s: StandardFramework) -> StandardFramework { + use std::{ + thread, + time::Duration, + }; + + thread::spawn(|| { + thread::sleep(Duration::from_secs(10)); + + loop { + debug!("starting token maintenance"); + if let Err(e) = maintain_token() { + error!("maintaining google access token: {}", e); + } + debug!("token maintenance complete"); + + thread::sleep(Duration::from_secs(60 * 2)); + } + }); + + s.command("game", |c| c + .known_as("gaem") + .desc("what game should we play?") + .exec(game) + ) +} + +fn game(_ctx: &mut Context, msg: &Message, _args: Args) -> Result<()> { + use std::collections::HashSet; + use fnv::FnvHashMap; + + let guild = msg.channel_id.to_channel()? + .guild() + .ok_or(err_msg("couldn't find guild"))?; + + let guild = guild.read() + .guild() + .ok_or(err_msg("couldn't find guild"))?; + + let guild = guild + .read(); + + let pairs = guild + .voice_states + .iter() + .filter_map(|(uid, voice)| { + voice.channel_id.map(|cid| (*uid, cid)) + }) + .collect::<FnvHashMap<_, _>>(); + + let channel = pairs.get(&msg.author.id).unwrap_or(&*VOICE_CHANNEL_ID); + let mut users = HashSet::new(); + + pairs.iter().for_each(|(uid, cid)| { + if cid == channel { + users.insert(*uid); + } + }); + +// if users.len() < 2 { +// info!("too few users in voice chat to make game comparison"); +// send(msg.channel_id, "yer too lonely", msg.tts)?; +// return Ok(()); +// } + + lazy_static! { + static ref USER_MAP: FnvHashMap<UserId, String> = { + use serde_json::Value; + use std::str; + let map_bytes = include_bytes!("../user_id_mapping.json"); + + let v: Value = serde_json::from_str(str::from_utf8(&map_bytes[..]).unwrap()).unwrap(); + match v { + Value::Object(m) => { + m.iter() + .map(|(k, v)| match v { + Value::Number(n) => (UserId(n.as_u64().unwrap()), k.clone()), + _ => panic!("non-number in user id mapping"), + }) + .collect() + }, + _ => panic!("couldn't read user id mapping"), + } + }; + } + + use url::Url; + + let mut u = Url::parse( + &format!("https://sheets.googleapis.com/v4/spreadsheets/{}/values:batchGet", *SPREADSHEET_ID))?; + + u.query_pairs_mut() + .append_pair("ranges", "a1:p") + .append_pair("valueRenderOption", "UNFORMATTED_VALUE") + .append_pair("majorDimension", "COLUMNS"); + + let oauth_token = get_oauth_token()?; + + let mut req = reqwest::Request::new(reqwest::Method::GET, u); + req.headers_mut().insert("Authorization", reqwest::header::HeaderValue::from_str(&format!("Bearer {}", oauth_token))?); + + let client = reqwest::Client::new(); + + let mut resp = client.execute(req)?; + + #[derive(Deserialize)] + struct Resp { + #[serde(rename = "valueRanges")] + value_ranges: Inner, + } + + #[derive(Deserialize)] + struct Inner { + values: Vec<Vec<String>>, + } + + let data = resp.json::<Resp>()?.value_ranges.values; + + use itertools::Itertools; + info!("data: {}", data.iter().map(|row| row.iter().join(" ")).join("\n")); + + Ok(()) +} + +lazy_static! { + static ref CONFIG: Config = Config::new( + SHEETS_CLIENT_ID.as_ref(), + SHEETS_SECRET.as_ref(), + "https://accounts.google.com/o/oauth2/v2/auth", + "https://www.googleapis.com/oauth2/v4/token", + ) + .add_scope("https://www.googleapis.com/auth/spreadsheets.readonly") + .set_redirect_url(REDIRECT_URL); +} + +fn get_oauth_token() -> Result<String> { + use std::{ + net::TcpListener, + }; + + use url::Url; + use chrono; + + use diesel::{ + NotFound, + result::Error as DieselError, + }; + + use crate::db; + + + lazy_static! { + static ref AUTH_URL: Url = { + let mut u = CONFIG.authorize_url(); + u.query_pairs_mut() + .append_pair("access_type", "offline"); + + u + }; + } + + #[cfg(debug_assertions)] + const PORT: u16 = 8080; + + #[cfg(not(debug_assertions))] + const PORT: u16 = 8981; + + let conn = db::connection()?; + + let token = db::GoogleOAuthToken::latest(&conn); + + match token { + Ok(t) => return Ok(t.token), + Err(e) => { + if let Some(NotFound) = e.downcast_ref::<DieselError>() { + info!("no token found in database"); + } else { + return Err(e); + } + } + } + + eprintln!("please navigate to {} in your browser", AUTH_URL.as_str()); + + let listener = TcpListener::bind(&format!("127.0.0.1:{}", PORT))?; + + const ATTEMPTS: usize = 10; + let code = listener.incoming() + .filter_map(|s| s.ok()) + .map(|mut stream| { + use std::io::{ + BufReader, + BufRead, + Write, + }; + + let mut request_line = String::new(); + + { + let mut reader = BufReader::new(&stream); + reader.read_line(&mut request_line).ok()?; + } + + let url = + Url::parse(&format!("http://localhost{}", request_line.split_whitespace().nth(1)?)).ok()?; + + let code = url.query_pairs() + .find(|(key, _)| key == "code") + .map(|(_, code)| code.into_owned()); + + let message = "all set"; + let resp = format!( + "HTTP/1.1 20 OK\r\ncontent-length: {}\r\n\r\n{}", + message.len(), + message, + ); + + stream.write_all(resp.as_bytes()).ok()?; + + code + }) + .take(ATTEMPTS) + .find(|x| !x.is_none()); + + let code = match code { + None => return Err(err_msg(format!("couldn't acquire oauth code from google after {} attempts", ATTEMPTS))), + Some(c) => c.unwrap(), + }; + + let token = CONFIG.exchange_code(code)?; + + if token.expires_in.is_none() || token.refresh_token.is_none() { + return Err(err_msg("token expiration or refresh token was missing")); + } + + let now = chrono::Utc::now().naive_utc(); + let new_expiration = token.expires_in + .map(|exp_sec| now + chrono::Duration::seconds(exp_sec.into())) + .unwrap(); + + let result = db::GoogleOAuthToken::create(&conn, token.access_token, token.refresh_token.unwrap(), new_expiration)?; + + Ok(result.token) +} + +fn maintain_token() -> Result<()> { + use diesel::{ + Connection, + result::Error as DieselError, + NotFound, + }; + + use chrono; + + use crate::db; + + let conn = db::connection()?; + + conn.transaction(|| { + let latest_token = db::GoogleOAuthToken::latest(&conn); + let latest_token = match latest_token { + Ok(t) => t, + Err(e) => { + if let Some(NotFound) = e.downcast_ref::<DieselError>() { + info!("maintaining google auth: no token to refresh found in database"); + return Ok(()); + } + + return Err(e); + } + }; + + let now = chrono::Utc::now().naive_utc(); + let diff = latest_token.expiration - now; + + if diff > chrono::Duration::minutes(10) { + info!("token has {} minutes remaining: not refreshing", diff.num_minutes()); + return Ok(()); + } + + let new_token = CONFIG.exchange_refresh_token(latest_token.refresh_token)?; + + if new_token.refresh_token.is_none() || new_token.expires_in.is_none() { + return Err(err_msg("refreshed token missing refresh token or expiration")); + } + + info!("received new token from google"); + + let new_expiration = new_token.expires_in + .map(|exp_sec| now + chrono::Duration::seconds(exp_sec.into())) + .unwrap(); + + db::GoogleOAuthToken::create(&conn, + new_token.access_token, + new_token.refresh_token.unwrap(), + new_expiration)?; + + Ok(()) + }) +} diff --git a/src/main.rs b/src/main.rs index 4515c6d..598b94f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,12 +13,17 @@ extern crate dotenv; extern crate either; #[macro_use] extern crate failure; extern crate fern; +extern crate fnv; #[cfg_attr(test, macro_use)] extern crate itertools; #[macro_use] extern crate lazy_static; #[macro_use] extern crate log; #[macro_use] extern crate nom; +#[cfg(feature = "games")] +extern crate oauth2; extern crate rand; extern crate regex; +extern crate reqwest; +#[macro_use] extern crate serde; extern crate serde_json; extern crate serenity; extern crate sha1; @@ -45,7 +50,7 @@ use serenity::{ }, model::{ gateway::Ready, - id::{GuildId, UserId}, + id::{ChannelId, GuildId, UserId}, }, prelude::*, }; @@ -56,6 +61,19 @@ pub use self::util::*; #[cfg(feature = "diesel")] mod db; +#[cfg(feature = "games")] +mod game; + +#[cfg(not(feature = "games"))] +mod game { + use serenity::framework::StandardFramework; + + #[inline] + fn register(f: StandardFramework) -> StandardFramework { + return f + } +} + mod commands; mod util; mod audio; @@ -65,6 +83,7 @@ pub type Result<T> = ::std::result::Result<T, Error>; lazy_static! { static ref TARGET_GUILD: u64 = dotenv!("TARGET_GUILD").parse().expect("unable to parse TARGET_GUILD as u64"); static ref TARGET_GUILD_ID: GuildId = GuildId(*TARGET_GUILD); + static ref VOICE_CHANNEL_ID: ChannelId = ChannelId(must_env_lookup::<u64>("VOICE_CHANNEL")); } struct Handler; @@ -132,6 +151,8 @@ fn run() -> Result<()> { }); framework = register_commands(framework); + framework = game::register(framework); + client.with_framework(framework); let shard_manager = client.shard_manager.clone(); |
