binary-option-tools-core 0.1.3

The core of the `binary-options-tools` crate and the python library `BinaryOptionsToolsV2`.
Documentation
use core::fmt;

use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::{
    general::traits::Credentials,
    pocketoption::error::{PocketOptionError, PocketResult},
};

use super::regions::Regions;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SessionData {
    session_id: String,
    ip_address: String,
    user_agent: String,
    last_activity: u64,
}

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Demo {
    session: String,
    is_demo: u32,
    uid: u32,
    platform: u32,
}

#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Real {
    session: SessionData,
    is_demo: u32,
    uid: u32,
    platform: u32,
    raw: String,
}

#[derive(Debug, Serialize, Clone)]
#[serde(untagged)]
pub enum Ssid {
    Demo(Demo),
    Real(Real),
}

impl Ssid {
    pub fn parse(data: impl ToString) -> PocketResult<Self> {
        let data = data.to_string();
        let parsed = data
            .trim()
            .strip_prefix(r#"42["auth","#)
            .ok_or(PocketOptionError::SsidParsingError(
                "Error parsing ssid string into object".into(),
            ))?
            .strip_suffix("]")
            .ok_or(PocketOptionError::SsidParsingError(
                "Error parsing ssid string into object".into(),
            ))?;
        let ssid: Demo = serde_json::from_str(parsed)
            .map_err(|e| PocketOptionError::SsidParsingError(e.to_string()))?;
        if ssid.is_demo == 1 {
            Ok(Self::Demo(ssid))
        } else {
            let real = Real {
                raw: data,
                is_demo: ssid.is_demo,
                session: php_serde::from_bytes(ssid.session.as_bytes()).map_err(|e| {
                    PocketOptionError::SsidParsingError(format!("Error parsing session data, {e}"))
                })?,
                uid: ssid.uid,
                platform: ssid.platform,
            };
            Ok(Self::Real(real))
        }
    }

    pub async fn server(&self) -> PocketResult<String> {
        match self {
            Self::Demo(_) => Ok(Regions::DEMO.to_string()),
            Self::Real(_) => Regions.get_server().await.map(|s| s.to_string()),
        }
    }

    pub async fn servers(&self) -> PocketResult<Vec<String>> {
        match self {
            Self::Demo(_) => Ok(vec![Regions::DEMO.to_string()]),
            Self::Real(_) => Ok(Regions
                .get_servers()
                .await?
                .iter()
                .map(|s| s.to_string())
                .collect()),
        }
    }

    pub fn user_agent(&self) -> String {
        match self {
            Self::Demo(_) => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36".into(),
            Self::Real(real) => real.session.user_agent.clone()
        }
    }

    pub fn demo(&self) -> bool {
        match self {
            Self::Demo(_) => true,
            Self::Real(_) => false,
        }
    }
}
impl fmt::Display for Demo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let ssid = serde_json::to_string(&self).map_err(|_| fmt::Error)?;
        write!(f, r#"42["auth",{}]"#, ssid)
    }
}

impl fmt::Display for Real {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.raw)
    }
}

impl fmt::Display for Ssid {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Demo(demo) => demo.fmt(f),
            Self::Real(real) => real.fmt(f),
        }
    }
}

impl<'de> Deserialize<'de> for Ssid {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let data: Value = Value::deserialize(deserializer)?;
        Ssid::parse(data).map_err(serde::de::Error::custom)
    }
}

impl Credentials for Ssid {}

#[cfg(test)]
mod tests {
    use super::*;
    use std::error::Error;

    #[test]
    fn test_descerialize_session() -> Result<(), Box<dyn Error>> {
        let session_raw = b"a:4:{s:10:\"session_id\";s:32:\"ae3aa847add89c341ec18d8ae5bf8527\";s:10:\"ip_address\";s:15:\"191.113.157.139\";s:10:\"user_agent\";s:120:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.\";s:13:\"last_activity\";i:1732926685;}31666d2dc07fdd866353937b97901e2b";
        let session: SessionData = php_serde::from_bytes(session_raw)?;
        dbg!(&session);
        let session_php = php_serde::to_vec(&session)?;
        dbg!(String::from_utf8(session_php).unwrap());
        Ok(())
    }

    #[test]
    fn test_parse_ssid() -> Result<(), Box<dyn Error>> {
        let ssids = [
            r#"42["auth",{"session":"looc69ct294h546o368s0lct7d","isDemo":1,"uid":87742848,"platform":2}]	"#,
            r#"42["auth",{"session":"a:4:{s:10:\"session_id\";s:32:\"ae3aa847add89c341ec18d8ae5bf8527\";s:10:\"ip_address\";s:15:\"191.113.157.139\";s:10:\"user_agent\";s:120:\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 OPR/114.\";s:13:\"last_activity\";i:1732926685;}31666d2dc07fdd866353937b97901e2b","isDemo":0,"uid":87742848,"platform":2}]	"#,
            r#"42["auth",{"session":"vtftn12e6f5f5008moitsd6skl","isDemo":1,"uid":27658142,"platform":2}]"#,
        ];
        for ssid in ssids {
            let valid = Ssid::parse(ssid)?;
            dbg!(valid);
        }
        Ok(())
    }
}