use std::path::Path;
use mkt_core::config::MktConfig;
use mkt_core::error::MktError;
pub fn load_config(config_path: Option<&Path>) -> anyhow::Result<MktConfig> {
Ok(if let Some(path) = config_path {
MktConfig::load_from_file(path)?
} else {
MktConfig::load()?
})
}
#[cfg(feature = "meta")]
pub fn build_meta(
config: &MktConfig,
profile_name: &str,
) -> anyhow::Result<mkt_meta::MetaProvider> {
use mkt_core::auth;
use secrecy::ExposeSecret;
let profile = config.profile(profile_name).ok();
let meta_config = profile.and_then(|p| p.meta.as_ref());
let token = auth::resolve_token(
"meta",
"MKT_META_ACCESS_TOKEN",
meta_config.and_then(|c| c.access_token.as_deref()),
)?;
let ad_account_id = meta_config
.and_then(|c| c.ad_account_id.clone())
.or_else(|| std::env::var("MKT_META_AD_ACCOUNT_ID").ok())
.unwrap_or_else(|| "act_unknown".to_string());
let api_version = meta_config
.and_then(|c| c.api_version.as_deref())
.unwrap_or("v25.0");
let mut client = mkt_meta::MetaClient::new(
secrecy::SecretString::from(token.expose_secret().to_string()),
ad_account_id,
Some(api_version),
)?;
let app_secret = std::env::var("MKT_META_APP_SECRET")
.ok()
.or_else(|| meta_config.and_then(|c| c.app_secret.clone()));
if let Some(secret) = app_secret {
client = client.with_app_secret(secrecy::SecretString::from(secret));
}
let page_id = meta_config
.and_then(|c| c.page_id.clone())
.or_else(|| std::env::var("MKT_META_PAGE_ID").ok());
let ig_user_id = meta_config
.and_then(|c| c.ig_user_id.clone())
.or_else(|| std::env::var("MKT_META_IG_USER_ID").ok());
Ok(mkt_meta::MetaProvider::new(client, page_id, ig_user_id))
}
#[cfg(feature = "google")]
pub async fn build_google(
config: &MktConfig,
profile_name: &str,
) -> anyhow::Result<mkt_google::GoogleProvider> {
let profile = config.profile(profile_name).ok();
let google_config = profile.and_then(|p| p.google.as_ref());
let developer_token = std::env::var("MKT_GOOGLE_DEVELOPER_TOKEN")
.ok()
.or_else(|| google_config.and_then(|c| c.developer_token.clone()))
.ok_or_else(|| {
MktError::auth_error(
"google",
"No developer token found. Set MKT_GOOGLE_DEVELOPER_TOKEN or configure it \
in your profile.",
)
})?;
let customer_id = std::env::var("MKT_GOOGLE_CUSTOMER_ID")
.ok()
.or_else(|| google_config.and_then(|c| c.customer_id.clone()))
.ok_or_else(|| {
MktError::auth_error(
"google",
"No customer ID found. Set MKT_GOOGLE_CUSTOMER_ID or configure it in your \
profile.",
)
})?;
let access_token = if let Ok(token) = std::env::var("MKT_GOOGLE_ACCESS_TOKEN") {
secrecy::SecretString::from(token)
} else {
let trio_from_env = (
std::env::var("MKT_GOOGLE_CLIENT_ID").ok(),
std::env::var("MKT_GOOGLE_CLIENT_SECRET").ok(),
std::env::var("MKT_GOOGLE_REFRESH_TOKEN").ok(),
);
let (client_id, client_secret, refresh_token) = match trio_from_env {
(Some(id), Some(secret), Some(refresh)) => (id, secret, refresh),
_ => google_config
.and_then(|c| {
Some((
c.client_id.clone()?,
c.client_secret.clone()?,
c.refresh_token.clone()?,
))
})
.ok_or_else(|| {
MktError::auth_error(
"google",
"No access token found. Set MKT_GOOGLE_ACCESS_TOKEN, set the \
MKT_GOOGLE_CLIENT_ID/CLIENT_SECRET/REFRESH_TOKEN trio, or \
configure client_id, client_secret, and refresh_token in \
your profile.",
)
})?,
};
mkt_google::fetch_access_token(
&client_id,
&client_secret,
&refresh_token,
mkt_google::GOOGLE_TOKEN_URL,
)
.await?
};
let client = mkt_google::GoogleClient::new(access_token, developer_token, &customer_id, None)?;
Ok(mkt_google::GoogleProvider::new(client))
}
#[cfg(feature = "tiktok")]
pub fn build_tiktok(
config: &MktConfig,
profile_name: &str,
) -> anyhow::Result<mkt_tiktok::TikTokProvider> {
use mkt_core::auth;
use secrecy::ExposeSecret;
let profile = config.profile(profile_name).ok();
let tt_config = profile.and_then(|p| p.tiktok.as_ref());
let token = auth::resolve_token(
"tiktok",
"MKT_TIKTOK_ACCESS_TOKEN",
tt_config.and_then(|c| c.access_token.as_deref()),
)?;
let advertiser_id = std::env::var("MKT_TIKTOK_ADVERTISER_ID")
.ok()
.or_else(|| tt_config.and_then(|c| c.advertiser_id.clone()))
.ok_or_else(|| {
MktError::auth_error(
"tiktok",
"No advertiser ID found. Set MKT_TIKTOK_ADVERTISER_ID or configure it \
in your profile.",
)
})?;
let client = mkt_tiktok::TikTokClient::new(
secrecy::SecretString::from(token.expose_secret().to_string()),
advertiser_id,
)?;
Ok(mkt_tiktok::TikTokProvider::new(client))
}
#[cfg(feature = "linkedin")]
pub async fn build_linkedin(
config: &MktConfig,
profile_name: &str,
) -> anyhow::Result<mkt_linkedin::LinkedInProvider> {
let profile = config.profile(profile_name).ok();
let li_config = profile.and_then(|p| p.linkedin.as_ref());
let access_token = if let Ok(token) = std::env::var("MKT_LINKEDIN_ACCESS_TOKEN") {
secrecy::SecretString::from(token)
} else if let Some(token) = li_config.and_then(|c| c.access_token.clone()) {
secrecy::SecretString::from(token)
} else {
let trio_from_env = (
std::env::var("MKT_LINKEDIN_CLIENT_ID").ok(),
std::env::var("MKT_LINKEDIN_CLIENT_SECRET").ok(),
std::env::var("MKT_LINKEDIN_REFRESH_TOKEN").ok(),
);
let (client_id, client_secret, refresh_token) = match trio_from_env {
(Some(id), Some(secret), Some(refresh)) => (id, secret, refresh),
_ => li_config
.and_then(|c| {
Some((
c.client_id.clone()?,
c.client_secret.clone()?,
c.refresh_token.clone()?,
))
})
.ok_or_else(|| {
MktError::auth_error(
"linkedin",
"No access token found. Set MKT_LINKEDIN_ACCESS_TOKEN, set the \
MKT_LINKEDIN_CLIENT_ID/CLIENT_SECRET/REFRESH_TOKEN trio, or \
configure client_id, client_secret, and refresh_token in \
your profile.",
)
})?,
};
mkt_linkedin::refresh_access_token(
&client_id,
&secrecy::SecretString::from(client_secret),
&secrecy::SecretString::from(refresh_token),
mkt_linkedin::LINKEDIN_TOKEN_URL,
)
.await?
};
let ad_account_id = std::env::var("MKT_LINKEDIN_AD_ACCOUNT_ID")
.ok()
.or_else(|| li_config.and_then(|c| c.ad_account_id.clone()))
.ok_or_else(|| {
MktError::auth_error(
"linkedin",
"No ad account ID found. Set MKT_LINKEDIN_AD_ACCOUNT_ID or configure it \
in your profile.",
)
})?;
let client = mkt_linkedin::LinkedInClient::new(access_token, ad_account_id)?;
Ok(mkt_linkedin::LinkedInProvider::new(client))
}