aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Perry <avaglir@gmail.com>2019-03-08 16:57:27 -0500
committerNathan Perry <avaglir@gmail.com>2019-03-08 16:57:27 -0500
commit86025df1f6d814c98a14211ceb4da6cf6de915c7 (patch)
treec615b6634eea05498d772f68aec2d52fa3ca4f01
parent24a7cb3c8eb0517b69c145170765c891a82ccc76 (diff)
first pass with oauth
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock75
-rw-r--r--Cargo.toml6
-rw-r--r--migrations/2019-03-08-073831_store-token/down.sql2
-rw-r--r--migrations/2019-03-08-073831_store-token/up.sql10
-rw-r--r--src/db/models.rs43
-rw-r--r--src/db/schema.rs11
-rw-r--r--src/game.rs330
-rw-r--r--src/main.rs23
9 files changed, 499 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 2d48ea0..674c719 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,6 @@
-
/target/
**/*.rs.bk
.env
.vscode
*.log
+user_id_mapping.json
diff --git a/Cargo.lock b/Cargo.lock
index 987871f..fc0b444 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 7b8bc33..a102eaa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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();