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
|
||||
.#*
|
||||
/target
|
||||
.patreon.json
|
||||
|
|
|
@ -1514,6 +1514,7 @@ dependencies = [
|
|||
"tokio",
|
||||
"tracing",
|
||||
"tracing-futures",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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<Object<Campaign>>;
|
||||
pub type Pledges = Vec<Object<Pledge>>;
|
||||
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[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<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(),
|
||||
base_url: "https://api.patreon.com".into(),
|
||||
creds: creds,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
|
@ -157,4 +184,57 @@ impl Client {
|
|||
let data: Data<Vec<Object<Pledge>>, Object<User>> = serde_json::from_str(&data)?;
|
||||
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)]
|
||||
pub struct Config {
|
||||
#[serde(rename = "clackSet")]
|
||||
pub(crate) clack_set: Vec<String>,
|
||||
pub(crate) signalboost: Vec<Person>,
|
||||
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<Option<patreon::Users>> {
|
|||
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) => {
|
||||
|
|
|
@ -36,7 +36,7 @@ impl Into<jsonfeed::Item> 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<mi::Client>) -> Result<Post> {
|
||||
debug!(
|
||||
"loading {}/{}",
|
||||
dir,
|
||||
"loading {}",
|
||||
fname.clone().into_os_string().into_string().unwrap()
|
||||
);
|
||||
|
||||
|
|
Loading…
Reference in New Issue