forked from cadey/xesite
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 <me@christine.website>
This commit is contained in:
parent
0c0c5875e6
commit
1c8c3396a7
|
@ -6,3 +6,4 @@ cw.tar
|
||||||
/result
|
/result
|
||||||
.#*
|
.#*
|
||||||
/target
|
/target
|
||||||
|
.patreon.json
|
||||||
|
|
|
@ -1514,6 +1514,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-futures",
|
"tracing-futures",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -14,6 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
|
url = "2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
|
use std::{fs, io, path::Path};
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, error, instrument};
|
use tracing::{debug, error, instrument};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub type Campaigns = Vec<Object<Campaign>>;
|
pub type Campaigns = Vec<Object<Campaign>>;
|
||||||
pub type Pledges = Vec<Object<Pledge>>;
|
pub type Pledges = Vec<Object<Pledge>>;
|
||||||
|
@ -61,14 +64,30 @@ pub struct User {
|
||||||
pub url: String,
|
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<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("json error: {0:?}")]
|
#[error("json error: {0}")]
|
||||||
Json(#[from] serde_json::Error),
|
Json(#[from] serde_json::Error),
|
||||||
#[error("request error: {0:?}")]
|
|
||||||
|
#[error("request error: {0}")]
|
||||||
Request(#[from] reqwest::Error),
|
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)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||||
|
@ -105,12 +124,20 @@ pub struct Links {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new(creds: Credentials) -> Self {
|
pub fn new(creds: Credentials) -> Result<Self> {
|
||||||
Self {
|
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(),
|
cli: reqwest::Client::new(),
|
||||||
base_url: "https://api.patreon.com".into(),
|
base_url: "https://api.patreon.com".into(),
|
||||||
creds: creds,
|
creds: creds,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
|
@ -157,4 +184,57 @@ impl Client {
|
||||||
let data: Data<Vec<Object<Pledge>>, Object<User>> = serde_json::from_str(&data)?;
|
let data: Data<Vec<Object<Pledge>>, Object<User>> = serde_json::from_str(&data)?;
|
||||||
Ok(data.included.unwrap())
|
Ok(data.included.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
POST www.patreon.com/api/oauth2/token
|
||||||
|
?grant_type=refresh_token
|
||||||
|
&refresh_token=<the user‘s refresh_token>
|
||||||
|
&client_id=<your client id>
|
||||||
|
&client_secret=<your 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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,9 @@ pub mod poke;
|
||||||
|
|
||||||
#[derive(Clone, Deserialize)]
|
#[derive(Clone, Deserialize)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
#[serde(rename = "clackSet")]
|
|
||||||
pub(crate) clack_set: Vec<String>,
|
|
||||||
pub(crate) signalboost: Vec<Person>,
|
pub(crate) signalboost: Vec<Person>,
|
||||||
pub(crate) port: u16,
|
|
||||||
#[serde(rename = "resumeFname")]
|
#[serde(rename = "resumeFname")]
|
||||||
pub(crate) resume_fname: PathBuf,
|
pub(crate) resume_fname: PathBuf,
|
||||||
#[serde(rename = "webMentionEndpoint")]
|
|
||||||
pub(crate) webmention_url: String,
|
|
||||||
#[serde(rename = "miToken")]
|
#[serde(rename = "miToken")]
|
||||||
pub(crate) mi_token: String,
|
pub(crate) mi_token: String,
|
||||||
}
|
}
|
||||||
|
@ -27,7 +22,9 @@ async fn patrons() -> Result<Option<patreon::Users>> {
|
||||||
let creds: Credentials = envy::prefixed("PATREON_")
|
let creds: Credentials = envy::prefixed("PATREON_")
|
||||||
.from_env()
|
.from_env()
|
||||||
.unwrap_or(Credentials::default());
|
.unwrap_or(Credentials::default());
|
||||||
let cli = Client::new(creds);
|
let mut cli = Client::new(creds)?;
|
||||||
|
|
||||||
|
cli.refresh_token().await?;
|
||||||
|
|
||||||
match cli.campaign().await {
|
match cli.campaign().await {
|
||||||
Ok(camp) => {
|
Ok(camp) => {
|
||||||
|
|
|
@ -36,7 +36,7 @@ impl Into<jsonfeed::Item> for Post {
|
||||||
.date_published(self.date.to_rfc3339())
|
.date_published(self.date.to_rfc3339())
|
||||||
.author(
|
.author(
|
||||||
jsonfeed::Author::new()
|
jsonfeed::Author::new()
|
||||||
.name("Christine Dodrill")
|
.name("Xe Iaso")
|
||||||
.url("https://christine.website")
|
.url("https://christine.website")
|
||||||
.avatar("https://christine.website/static/img/avatar.png"),
|
.avatar("https://christine.website/static/img/avatar.png"),
|
||||||
);
|
);
|
||||||
|
@ -83,8 +83,7 @@ impl Post {
|
||||||
|
|
||||||
async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> {
|
async fn read_post(dir: &str, fname: PathBuf, cli: &Option<mi::Client>) -> Result<Post> {
|
||||||
debug!(
|
debug!(
|
||||||
"loading {}/{}",
|
"loading {}",
|
||||||
dir,
|
|
||||||
fname.clone().into_os_string().into_string().unwrap()
|
fname.clone().into_os_string().into_string().unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue