use std::time::{SystemTime, UNIX_EPOCH};
use lazy_static::lazy_static;
use log::{debug, warn};
use reqwest::Client;
use serde::Serialize;
use crate::config::Config;
const MEASUREMENT_ID: &str = "G-2QQN7V5WE1";
const API_KEY: Option<&str> = option_env!("ANALYTICS_API_KEY");
lazy_static! {
static ref ANALYTICS_ID: String = uuid::Uuid::new_v4().hyphenated().to_string();
}
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum AnalyticsEventName {
ModInstall,
ModRequiredInstall,
ModPrereleaseInstall,
ModReinstall,
ModUpdate,
}
#[derive(Debug, Serialize)]
struct AnalyticsEventParams {
mod_unique_name: String,
manager_version: String,
}
#[derive(Debug, Serialize)]
struct AnalyticsEvent {
name: AnalyticsEventName,
params: AnalyticsEventParams,
}
#[derive(Debug, Serialize)]
struct AnalyticsPayload {
client_id: String,
timestamp_micros: u128,
non_personalized_ads: bool,
events: Vec<AnalyticsEvent>,
}
impl AnalyticsPayload {
pub fn new(event_name: &AnalyticsEventName, unique_name: &str) -> Self {
Self {
client_id: ANALYTICS_ID.to_string(),
timestamp_micros: SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_micros(),
non_personalized_ads: true,
events: vec![AnalyticsEvent {
name: event_name.to_owned(),
params: AnalyticsEventParams {
mod_unique_name: unique_name.to_string(),
manager_version: env!("CARGO_PKG_VERSION").to_string(),
},
}],
}
}
}
pub async fn send_analytics_event(
event_name: AnalyticsEventName,
unique_name: &str,
is_disabled: bool,
) {
if is_disabled {
debug!("Skipping Analytics As It's Disabled");
return;
}
if let Some(api_key) = API_KEY {
let url = format!(
"https://www.google-analytics.com/mp/collect?measurement_id={MEASUREMENT_ID}&api_secret={api_key}"
);
let client = Client::new();
let payload = AnalyticsPayload::new(&event_name, unique_name);
debug!("Sending {payload:?}");
let resp = client.post(url).json(&payload).send().await;
match resp {
Ok(resp) => {
if resp.status().is_success() {
debug!("Successfully Sent Analytics Event {event_name:?} for {unique_name}");
} else {
warn!(
"Couldn't Send Analytics Event For {}! {}",
unique_name,
resp.status()
)
}
}
Err(why) => {
let err_text = format!("Couldn't Send Analytics Event For {unique_name}! {why:?}")
.replace(api_key, "***");
warn!("{err_text}");
}
}
} else {
debug!("Skipping Analytics As The ANALYTICS_API_KEY Is Null ({event_name:?})");
}
}
pub async fn send_analytics_deferred(
event: AnalyticsEventName,
unique_name: impl Into<String>,
config: &Config,
) {
let unique_name = unique_name.into();
let should_skip = !config.send_analytics;
tokio::spawn(async move {
send_analytics_event(event, &unique_name, should_skip).await;
});
}