From 1c8c3396a735bd373be417df2138607f0206b736 Mon Sep 17 00:00:00 2001 From: Xe Iaso Date: Sat, 2 Apr 2022 16:15:10 +0000 Subject: [PATCH] lib/patreon: refresh token support This should hopefully make the patrons page work consistently and no longer require me to manually update the patreon token once per month. Why didn't I do this age ago?????? Hacked up live on twitch: https://twitch.tv/princessxen Closes #442 Signed-off-by: Xe --- .gitignore | 1 + Cargo.lock | 1 + lib/patreon/Cargo.toml | 1 + lib/patreon/src/lib.rs | 90 +++++++++++++++++++++++++++++++++++++++--- src/app/mod.rs | 9 ++--- src/post/mod.rs | 5 +-- 6 files changed, 93 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 44098b9..049d0ed 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ cw.tar /result .#* /target +.patreon.json diff --git a/Cargo.lock b/Cargo.lock index 69f652d..d9a4b72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1514,6 +1514,7 @@ dependencies = [ "tokio", "tracing", "tracing-futures", + "url", ] [[package]] diff --git a/lib/patreon/Cargo.toml b/lib/patreon/Cargo.toml index ea2f115..e5907b1 100644 --- a/lib/patreon/Cargo.toml +++ b/lib/patreon/Cargo.toml @@ -14,6 +14,7 @@ serde = { version = "1", features = ["derive"] } thiserror = "1" tracing = "0.1" tracing-futures = "0.2" +url = "2" [dev-dependencies] tokio = { version = "1", features = ["full"] } diff --git a/lib/patreon/src/lib.rs b/lib/patreon/src/lib.rs index 3c504fe..df40e88 100644 --- a/lib/patreon/src/lib.rs +++ b/lib/patreon/src/lib.rs @@ -1,7 +1,10 @@ +use std::{fs, io, path::Path}; + use chrono::prelude::*; use serde::{Deserialize, Serialize}; use thiserror::Error; use tracing::{debug, error, instrument}; +use url::Url; pub type Campaigns = Vec>; pub type Pledges = Vec>; @@ -61,14 +64,30 @@ pub struct User { pub url: String, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct RefreshGrant { + pub access_token: String, + pub refresh_token: String, + pub expires_in: serde_json::Value, + pub scope: serde_json::Value, + pub token_type: String, +} + pub type Result = std::result::Result; #[derive(Error, Debug)] pub enum Error { - #[error("json error: {0:?}")] + #[error("json error: {0}")] Json(#[from] serde_json::Error), - #[error("request error: {0:?}")] + + #[error("request error: {0}")] Request(#[from] reqwest::Error), + + #[error("{0}")] + IO(#[from] io::Error), + + #[error("url parse error: {0}")] + URLParse(#[from] url::ParseError), } #[derive(Debug, Serialize, Deserialize, Clone, Default)] @@ -105,12 +124,20 @@ pub struct Links { } impl Client { - pub fn new(creds: Credentials) -> Self { - Self { + pub fn new(creds: Credentials) -> Result { + let mut creds = creds.clone(); + + let p = Path::new(".patreon.json"); + if p.exists() { + let config = fs::read_to_string(p)?; + creds = serde_json::from_str(&config)?; + } + + Ok(Self { cli: reqwest::Client::new(), base_url: "https://api.patreon.com".into(), creds: creds, - } + }) } #[instrument(skip(self))] @@ -157,4 +184,57 @@ impl Client { let data: Data>, Object> = serde_json::from_str(&data)?; Ok(data.included.unwrap()) } + + /* + POST www.patreon.com/api/oauth2/token + ?grant_type=refresh_token + &refresh_token= + &client_id= + &client_secret= + + 1. grab new creds + 2. serialize new creds to disk + 3. reload current creds in ram + 4. ??? + 5. profit! + */ + #[instrument(skip(self))] + pub async fn refresh_token(&mut self) -> Result<()> { + let mut u = Url::parse(&self.base_url)?; + u.set_path("/api/oauth2/token"); + u.query_pairs_mut() + .append_pair("grant_type", "refresh_token") + .append_pair("refresh_token", &self.creds.refresh_token) + .append_pair("client_id", &self.creds.client_id) + .append_pair("client_secret", &self.creds.client_secret); + + let rg: RefreshGrant = self + .cli + .post(&u.to_string()) + .header( + "Authorization", + format!("Bearer {}", self.creds.access_token), + ) + .send() + .await? + .error_for_status()? + .json() + .await?; + + let mut creds = self.creds.clone(); + + creds.access_token = rg.access_token; + creds.refresh_token = rg.refresh_token; + + let p = Path::new(".patreon.json"); + if p.exists() { + fs::remove_file(p)?; + } + let mut fout = fs::File::create(p)?; + serde_json::to_writer(&mut fout, &creds)?; + + self.creds = creds; + + Ok(()) + } } diff --git a/src/app/mod.rs b/src/app/mod.rs index 2c4abcd..429018c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -9,14 +9,9 @@ pub mod poke; #[derive(Clone, Deserialize)] pub struct Config { - #[serde(rename = "clackSet")] - pub(crate) clack_set: Vec, pub(crate) signalboost: Vec, - pub(crate) port: u16, #[serde(rename = "resumeFname")] pub(crate) resume_fname: PathBuf, - #[serde(rename = "webMentionEndpoint")] - pub(crate) webmention_url: String, #[serde(rename = "miToken")] pub(crate) mi_token: String, } @@ -27,7 +22,9 @@ async fn patrons() -> Result> { let creds: Credentials = envy::prefixed("PATREON_") .from_env() .unwrap_or(Credentials::default()); - let cli = Client::new(creds); + let mut cli = Client::new(creds)?; + + cli.refresh_token().await?; match cli.campaign().await { Ok(camp) => { diff --git a/src/post/mod.rs b/src/post/mod.rs index 1cd7f92..a152bc9 100644 --- a/src/post/mod.rs +++ b/src/post/mod.rs @@ -36,7 +36,7 @@ impl Into for Post { .date_published(self.date.to_rfc3339()) .author( jsonfeed::Author::new() - .name("Christine Dodrill") + .name("Xe Iaso") .url("https://christine.website") .avatar("https://christine.website/static/img/avatar.png"), ); @@ -83,8 +83,7 @@ impl Post { async fn read_post(dir: &str, fname: PathBuf, cli: &Option) -> Result { debug!( - "loading {}/{}", - dir, + "loading {}", fname.clone().into_os_string().into_string().unwrap() );