binary_options_tools 0.2.0

High-level library for binary options trading automation. Supports PocketOption and ExpertOption with real-time data streaming, WebSocket API access, and automated trading strategies.
Documentation
use std::sync::Arc;

use binary_options_tools_core_pre::{
    connector::{Connector as ConnectorTrait, ConnectorError, ConnectorResult},
    reimports::{
        connect_async_tls_with_config, generate_key, Connector, MaybeTlsStream, Request,
        WebSocketStream,
    },
};
use futures_util::{stream::FuturesUnordered, StreamExt};
use tokio::net::TcpStream;
use tracing::{debug, warn};
use url::Url;

use crate::expertoptions::{regions::Regions, state::State};
use crate::utils::init_crypto_provider;

#[derive(Clone)]
pub struct ExpertConnect;

#[async_trait::async_trait]
impl ConnectorTrait<State> for ExpertConnect {
    async fn connect(
        &self,
        state: Arc<State>,
    ) -> ConnectorResult<WebSocketStream<MaybeTlsStream<TcpStream>>> {
        // Implement connection logic here
        let mut futures = FuturesUnordered::new();
        let url = Regions::regions_str().into_iter().map(String::from); // No demo region for ExpertOptions
        for u in url {
            futures.push(async {
                debug!(target: "ExpertConnectThread", "Connecting to ExpertOptions at {u}");
                try_connect(state.user_agent().await, u.clone())
                    .await
                    .map_err(|e| (e, u))
            });
        }
        while let Some(result) = futures.next().await {
            match result {
                Ok(stream) => {
                    debug!(target: "PocketConnect", "Successfully connected to ExpertOptions");
                    return Ok(stream);
                }
                Err((e, u)) => warn!(target: "PocketConnect", "Failed to connect to {}: {}", u, e),
            }
        }
        Err(ConnectorError::Custom(
            "Failed to connect to any of the provided URLs".to_string(),
        ))
    }

    async fn disconnect(&self) -> ConnectorResult<()> {
        // Implement disconnect logic if needed
        warn!(target: "ExpertConnect", "Disconnect method is not implemented yet and shouldn't be called.");
        Ok(())
    }
}

pub async fn try_connect(
    agent: String,
    url: String,
) -> ConnectorResult<WebSocketStream<MaybeTlsStream<TcpStream>>> {
    init_crypto_provider();
    let mut root_store = rustls::RootCertStore::empty();
    let certs_result = rustls_native_certs::load_native_certs();
    if !certs_result.errors.is_empty() {
        warn!(target: "ExpertConnect", "Some native certificates failed to load: {:?}", certs_result.errors);
    }
    let certs = certs_result.certs;
    if certs.is_empty() {
        return Err(ConnectorError::Custom(
            "Could not load any native certificates".to_string(),
        ));
    }
    for cert in certs {
        root_store.add(cert).ok();
    }
    let tls_config = rustls::ClientConfig::builder()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let connector = Connector::Rustls(std::sync::Arc::new(tls_config));

    let t_url = Url::parse(&url).map_err(|e| ConnectorError::UrlParsing(e.to_string()))?;
    let host = t_url
        .host_str()
        .ok_or(ConnectorError::UrlParsing("Host not found".into()))?;
    let request = Request::builder()
        .uri(t_url.to_string())
        .header("Origin", "https://app.expertoption.com")
        .header("Cache-Control", "no-cache")
        .header("User-Agent", agent)
        .header("Upgrade", "websocket")
        .header("Connection", "upgrade")
        .header("Sec-Websocket-Key", generate_key())
        .header("Sec-Websocket-Version", "13")
        .header("Host", host)
        .body(())
        .map_err(|e| ConnectorError::HttpRequestBuild(e.to_string()))?;

    let (ws, _) = connect_async_tls_with_config(request, None, false, Some(connector))
        .await
        .map_err(|e| ConnectorError::Custom(e.to_string()))?;
    Ok(ws)
}