use base64::Engine;
use base64::engine::general_purpose;
use hyper_tls::native_tls;
use thiserror::Error;
use prost::Message;
use crate::youtube::channel_continuation::Token;
use crate::youtube::ChannelContinuation;
use chrono::NaiveDate;
use std::net::{IpAddr, Ipv6Addr};
use rand::Rng;
use chrono::{DateTime, Duration, TimeZone, Utc};
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
static ref TIME_REGEX: Regex = Regex::new(
r"^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$"
).unwrap();
}
#[derive(Error, Debug)]
pub enum ClientError {
#[error("Network error: {0}")]
NetworkError(#[from] std::io::Error),
#[error("TLS error: {0}")]
TlsError(#[from] native_tls::Error),
}
pub fn get_rand_ipv6(subnet: &str, range_id: u16) -> Result<IpAddr, Box<dyn std::error::Error>> {
let parts: Vec<&str> = subnet.split('/').collect();
if parts.len() != 2 {
return Err("Invalid subnet format".into());
}
let ipv6: u128 = parts[0].parse::<Ipv6Addr>()?.into();
let prefix_len: u8 = parts[1].parse()?;
if prefix_len != 48 {
return Err("Only /48 subnets are supported".into());
}
let net_part = (ipv6 >> 80) << 80;
let range_part = (range_id as u128) << 64;
let rand: u128 = rand::thread_rng().gen();
let host_part = rand & ((1u128 << 64) - 1);
let result = net_part | range_part | host_part;
Ok(IpAddr::V6(result.into()))
}
pub fn parse_numeric_string(numeric_str: &str) -> i64 {
let numeric_str = numeric_str.trim();
let parts: Vec<&str> = numeric_str.split_whitespace().collect();
if parts.len() >= 1 {
let value_str = parts[0].replace(",", "");
value_str.parse().unwrap_or(0)
} else {
0
}
}
pub fn parse_multiplied_string(multiplied_str: &str) -> i64 {
let multiplied_str = multiplied_str.trim();
let multiplied_str = multiplied_str.split_whitespace().next().unwrap_or("0");
let multiplier: i64;
let value: f64;
if multiplied_str.ends_with('K') {
multiplier = 1000;
value = multiplied_str[..multiplied_str.len() - 1].parse().unwrap_or(0.0);
} else if multiplied_str.ends_with('M') {
multiplier = 1_000_000;
value = multiplied_str[..multiplied_str.len() - 1].parse().unwrap_or(0.0);
} else if multiplied_str.ends_with('B') {
multiplier = 1_000_000_000;
value = multiplied_str[..multiplied_str.len() - 1].parse().unwrap_or(0.0);
} else {
return multiplied_str.parse().unwrap_or(0);
}
(value * multiplier as f64) as i64
}
pub fn parse_creation_date(joined_date_str: &str) -> i32 {
let parts: Vec<&str> = joined_date_str.split_whitespace().collect();
if parts.len() == 0 {
return 0
}
if parts.len() == 3 {
let month = match parts[0] {
"Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4, "May" => 5, "Jun" => 6,
"Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10, "Nov" => 11, "Dec" => 12,
_ => 0,
};
let day: u32 = parts[1].trim_end_matches(',').parse().unwrap_or_default();
let year: i32 = parts[2].parse().unwrap_or_default();
NaiveDate::from_ymd_opt(year, month, day)
.and_then(|date| date.and_hms_opt(0, 0, 0))
.map(|date_time| date_time.and_utc().timestamp() as i32)
.unwrap_or_default()
} else {
0
}
}
pub fn generate_continuation_token(channel_id: String, request_token: String) -> String {
let token = Token {
channel_id,
request_token,
};
let continuation = ChannelContinuation {
token: Some(token),
};
let proto_bytes = continuation.encode_to_vec();
general_purpose::STANDARD.encode(proto_bytes)
}
pub fn relative_time_to_timestamp(input: &str) -> Result<i64, String> {
let now = Utc::now();
let input = input.trim().to_lowercase();
if let Some(captures) = TIME_REGEX.captures(&input) {
let amount: i64 = captures[1]
.parse()
.map_err(|_| "Failed to parse number")?;
let unit = &captures[2];
let result = match unit {
"second" => now - Duration::seconds(amount),
"minute" => now - Duration::minutes(amount),
"hour" => now - Duration::hours(amount),
"day" => now - Duration::days(amount),
"week" => now - Duration::weeks(amount),
"month" => now - Duration::days(amount * 30), "year" => now - Duration::days(amount * 365), _ => return Err("Invalid time unit".to_string())
};
Ok(result.timestamp())
} else {
Err("Invalid input format".to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_relative_time_parsing() {
let now = Utc::now();
let test_cases = vec![
("33 seconds ago", Duration::seconds(33)),
("4 minutes ago", Duration::minutes(4)),
("15 minutes ago", Duration::minutes(15)),
("10 hours ago", Duration::hours(10)),
("2 days ago", Duration::days(2)),
("3 weeks ago", Duration::weeks(3)),
("1 month ago", Duration::days(30)),
("9 years ago", Duration::days(9 * 365)),
];
for (input, expected_duration) in test_cases {
let result = relative_time_to_timestamp(input).unwrap();
let expected = (now - expected_duration).timestamp();
assert!((result - expected).abs() <= 1,
"Failed for input: {}", input);
}
}
}