botrs 0.12.2

A Rust QQ Bot framework based on QQ Guild Bot API
Documentation
use super::QQBotTokenSource;
use crate::error::Result;
use std::time::{Duration, SystemTime, UNIX_EPOCH};

const DEFAULT_EXPIRY_DELTA_MILLIS: u64 = 9_000;
const RAND_TIME_UPPER_LIMIT_MILLIS: u64 = 500;

#[allow(non_snake_case)]
pub async fn StartRefreshAccessToken(
    token_source: QQBotTokenSource,
) -> Result<tokio::task::JoinHandle<()>> {
    token_source.ensure_valid_token().await?;

    Ok(tokio::spawn(async move {
        let mut consecutive_failures = 0;
        loop {
            let refresh_millis = if consecutive_failures > 0 {
                if consecutive_failures > 10 {
                    panic!("get token failed continuously for more than ten times");
                }
                1_000
            } else {
                token_source
                    .cached_expires_in()
                    .await
                    .map(get_refresh_millis)
                    .unwrap_or(1_000)
            };

            tracing::debug!("refresh after {} milli sec", refresh_millis);
            tokio::time::sleep(Duration::from_millis(refresh_millis)).await;

            match token_source.force_refresh_access_token().await {
                Ok(()) => consecutive_failures = 0,
                Err(err) => {
                    consecutive_failures += 1;
                    tracing::error!("refresh access token failed: {}", err);
                }
            }
        }
    }))
}

pub(super) fn parse_expires_in(value: &serde_json::Value) -> Option<u64> {
    value
        .as_u64()
        .or_else(|| value.as_str().and_then(|value| value.parse().ok()))
}

pub(super) fn get_refresh_millis(token_ttl_secs: u64) -> u64 {
    let refresh_millis = token_ttl_secs.saturating_mul(1_000);
    if refresh_millis < DEFAULT_EXPIRY_DELTA_MILLIS {
        return refresh_millis;
    }

    let refresh_millis = refresh_millis - DEFAULT_EXPIRY_DELTA_MILLIS;
    if refresh_millis > RAND_TIME_UPPER_LIMIT_MILLIS {
        refresh_millis - jitter_millis(RAND_TIME_UPPER_LIMIT_MILLIS)
    } else {
        refresh_millis
    }
}

pub(super) fn jitter_millis(upper_bound: u64) -> u64 {
    if upper_bound == 0 {
        return 0;
    }

    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| u64::from(duration.subsec_nanos()) % upper_bound)
        .unwrap_or_default()
}