use std::path::PathBuf;
use async_trait::async_trait;
use nu_protocol::LabeledError;
use serde::{Deserialize, Serialize};
use twitch_irc::login::{TokenStorage, UserAccessToken};
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct ApplicationCredentials {
pub client_id: String,
pub(crate) client_secret: String,
}
impl ApplicationCredentials {
pub fn credentials_path() -> PathBuf {
dirs::data_local_dir()
.unwrap()
.join("nu_plugin_twitch")
.join("creds.json")
}
pub fn load() -> Result<Self, LabeledError> {
let json = std::fs::read_to_string(Self::credentials_path()).map_err(|e| {
LabeledError::new("Failed to read app.json file")
.with_inner(LabeledError::new(e.to_string()))
})?;
serde_json::from_str(&json).map_err(|e| {
LabeledError::new("Failed to read app credentials")
.with_inner(LabeledError::new(e.to_string()))
})
}
pub fn save(&self) -> Result<(), LabeledError> {
let creds_path = Self::credentials_path();
std::fs::create_dir_all(creds_path.clone().parent().unwrap()).map_err(|e| {
LabeledError::new("Failed to create LocalData directory")
.with_inner(LabeledError::new(e.to_string()))
})?;
let json = serde_json::to_string(self).map_err(|e| {
LabeledError::new("Failed to serialize app credentials")
.with_inner(LabeledError::new(e.to_string()))
})?;
std::fs::write(creds_path.clone(), json).map_err(|e| {
LabeledError::new(format!("Failed to write credentials to {creds_path:?}"))
.with_inner(LabeledError::new(e.to_string()))
})
}
}
#[derive(Debug)]
pub(crate) struct ProfileTokenProvider {
pub profile: Option<String>,
}
impl ProfileTokenProvider {
pub fn profile_name(&self) -> String {
self.profile.clone().unwrap_or("default".to_string())
}
pub fn profile_path(&self) -> PathBuf {
dirs::data_local_dir()
.unwrap()
.join("nu_plugin_twitch")
.join(format!("token_{}.json", self.profile_name()))
}
}
#[async_trait]
impl TokenStorage for ProfileTokenProvider {
type LoadError = LabeledError;
type UpdateError = LabeledError;
async fn load_token(&mut self) -> Result<UserAccessToken, Self::LoadError> {
let path = self.profile_path();
let json = std::fs::read_to_string(path.clone()).map_err(|e| {
LabeledError::new(format!("Failed to read token file {path:?}"))
.with_inner(LabeledError::new(e.to_string()))
.with_help(format!(
"Did you meant to use another profile than `{}`",
self.profile_name()
))
})?;
serde_json::from_str(&json).map_err(|e| {
LabeledError::new(format!("Failed to parse token file {path:?}"))
.with_inner(LabeledError::new(e.to_string()))
})
}
async fn update_token(&mut self, token: &UserAccessToken) -> Result<(), Self::UpdateError> {
let token_path = self.profile_path();
std::fs::create_dir_all(token_path.clone().parent().unwrap()).map_err(|e| {
LabeledError::new("Failed to create LocalData directory")
.with_inner(LabeledError::new(e.to_string()))
})?;
let json = serde_json::to_string(token).map_err(|e| {
LabeledError::new("Failed to serialize token")
.with_inner(LabeledError::new(e.to_string()))
})?;
std::fs::write(token_path.clone(), json).map_err(|e| {
LabeledError::new(format!("Failed to write token ot file {token_path:?}"))
.with_inner(LabeledError::new(e.to_string()))
})
}
}