chatgpt_api 0.3.0

Use ChatGPT easily from Rust. Or from the command line.
Documentation
use std::string::ToString;
use std::sync::Arc;

use regex::Regex;
use reqwest::cookie::{self};
use reqwest::header::{self, HeaderMap, HeaderValue};
use reqwest::Proxy;

pub struct Authenticator {
    pub session_token: Option<String>,
    email_address: String,
    password: String,
    session: reqwest::Client,
    pub access_token: Option<String>,
    user_agent: String,
    cookie_store: Arc<cookie::Jar>,
}

impl Authenticator {
    pub fn new(email_address: Option<&str>, password: Option<&str>, proxy: Option<Proxy>) -> Self {
        let cookie_store = Arc::new(cookie::Jar::default());
        let mut client = reqwest::Client::builder()
            .cookie_store(true)
            .cookie_provider(cookie_store.clone());
        if let Some(proxy) = proxy {
            client = client.proxy(proxy);
        }
        Self {
            session_token: None,
            email_address: email_address.unwrap_or("").to_string(),
            password: password.unwrap_or("").to_string(),
            session: client.build().unwrap(),
            access_token: None,
            user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 \
                         Safari/537.36"
                .to_string(),
            cookie_store,
        }
    }

    fn url_encode(string: &str) -> String {
        urlencoding::encode(string).to_string()
    }

    pub async fn begin(&mut self) {
        let url = "https://explorer.api.openai.com/api/auth/csrf";
        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "explorer.api.openai.com".parse().unwrap()),
            (header::ACCEPT, "*/*".parse().unwrap()),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (header::ACCEPT_LANGUAGE, "en-GB,en-US;q=0.9,en;q=0.8".parse().unwrap()),
            (
                header::REFERER,
                "https://explorer.api.openai.com/auth/login".parse().unwrap(),
            ),
            (header::ACCEPT_ENCODING, "gzip, deflate, br".parse().unwrap()),
        ]
        .into_iter()
        .collect();

        let response = self.session.get(url).headers(headers).send().await.unwrap();
        if response.status().is_success()
            && response
                .headers()
                .get("Content-Type")
                .unwrap()
                .to_str()
                .unwrap()
                .contains("json")
        {
            let csrf_token = response.json::<serde_json::Value>().await.unwrap()["csrfToken"]
                .as_str()
                .unwrap()
                .to_string();
            self.__part_one(&csrf_token).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_one(&mut self, token: &str) {
        let url = "https://explorer.api.openai.com/api/auth/signin/auth0?prompt=login";
        let payload = format!("callbackUrl=%2F&csrfToken={token}&json=true");
        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "explorer.api.openai.com".parse().unwrap()),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (
                header::CONTENT_TYPE,
                "application/x-www-form-urlencoded".parse().unwrap(),
            ),
            (header::ACCEPT, "*/*".parse().unwrap()),
            (header::HeaderName::from_static("Sec-Gpc"), "1".parse().unwrap()),
            (header::ACCEPT_LANGUAGE, "en-US,en;q=0.8".parse().unwrap()),
            (header::ORIGIN, "https://explorer.api.openai.com".parse().unwrap()),
            (
                header::HeaderName::from_static("Sec-Fetch-Site"),
                "same-origin".parse().unwrap(),
            ),
            (
                header::HeaderName::from_static("Sec-Fetch-Mode"),
                "cors".parse().unwrap(),
            ),
            (
                header::HeaderName::from_static("Sec-Fetch-Dest"),
                "empty".parse().unwrap(),
            ),
            (
                header::REFERER,
                "https://explorer.api.openai.com/auth/login".parse().unwrap(),
            ),
            (header::ACCEPT_ENCODING, "gzip, deflate".parse().unwrap()),
        ]
        .into_iter()
        .collect();

        let response = self
            .session
            .post(url)
            .headers(headers)
            .body(payload)
            .send()
            .await
            .unwrap();
        if response.status().is_success()
            && response
                .headers()
                .get("Content-Type")
                .unwrap()
                .to_str()
                .unwrap()
                .contains("json")
        {
            let url = response.json::<serde_json::Value>().await.unwrap()["url"]
                .as_str()
                .unwrap()
                .to_string();
            assert!(
                !(url == "https://explorer.api.openai.com/api/auth/error?error=OAuthSignin" || url.contains("error")),
                "You have been rate limited. Please try again later."
            );
            self.__part_two(&url).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_two(&mut self, url: &str) {
        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "auth0.openai.com".parse().unwrap()),
            (
                header::ACCEPT,
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
                    .parse()
                    .unwrap(),
            ),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (header::ACCEPT_LANGUAGE, "en-US,en;q=0.9".parse().unwrap()),
            (header::REFERER, "https://explorer.api.openai.com/".parse().unwrap()),
        ]
        .into_iter()
        .collect();

        let response = self.session.get(url).headers(headers).send().await.unwrap();
        if response.status().is_redirection() || response.status().is_success() {
            let state = Regex::new(r"state=(.*)")
                .unwrap()
                .captures(&response.text().await.unwrap())
                .unwrap()[1]
                .to_string();
            self.__part_three(&state).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_three(&mut self, state: &str) {
        let url = format!("https://auth0.openai.com/u/login/identifier?state={state}");

        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "auth0.openai.com".parse().unwrap()),
            (
                header::ACCEPT,
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
                    .parse()
                    .unwrap(),
            ),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (header::ACCEPT_LANGUAGE, "en-US,en;q=0.9".parse().unwrap()),
            (header::REFERER, "https://explorer.api.openai.com/".parse().unwrap()),
        ]
        .into_iter()
        .collect();

        let response = self.session.get(url).headers(headers).send().await.unwrap();
        if response.status().is_success() {
            self.__part_four(state).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_four(&mut self, state: &str) {
        let url = format!("https://auth0.openai.com/u/login/identifier?state={state}");
        let email_url_encoded = Self::url_encode(&self.email_address);

        let payload = format!(
            "state={state}&username={email_url_encoded}&js-available=false&webauthn-available=true&is-brave=false&\
             webauthn-platform-available=true&action=default"
        );

        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "auth0.openai.com".parse().unwrap()),
            (header::ORIGIN, "https://auth0.openai.com".parse().unwrap()),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (
                header::ACCEPT,
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
                    .parse()
                    .unwrap(),
            ),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (
                header::REFERER,
                format!("https://auth0.openai.com/u/login/identifier?state={state}")
                    .parse()
                    .unwrap(),
            ),
            (header::ACCEPT_LANGUAGE, "en-US,en;q=0.9".parse().unwrap()),
            (
                header::CONTENT_TYPE,
                "application/x-www-form-urlencoded".parse().unwrap(),
            ),
        ]
        .into_iter()
        .collect();

        let response = self
            .session
            .post(url)
            .headers(headers)
            .body(payload)
            .send()
            .await
            .unwrap();
        if response.status().is_redirection() || response.status().is_success() {
            self.__part_five(state).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_five(&mut self, state: &str) {
        let url = format!("https://auth0.openai.com/u/login/password?state={state}");
        let email_url_encoded = Self::url_encode(&self.email_address);
        let password_url_encoded = Self::url_encode(&self.password);
        let payload =
            format!("state={state}&username={email_url_encoded}&password={password_url_encoded}&action=default");

        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "auth0.openai.com".parse().unwrap()),
            (header::ORIGIN, "https://auth0.openai.com".parse().unwrap()),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (
                header::ACCEPT,
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
                    .parse()
                    .unwrap(),
            ),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (
                header::REFERER,
                format!("https://auth0.openai.com/u/login/password?state={state}")
                    .parse()
                    .unwrap(),
            ),
            (header::ACCEPT_LANGUAGE, "en-US,en;q=0.9".parse().unwrap()),
            (
                header::CONTENT_TYPE,
                "application/x-www-form-urlencoded".parse().unwrap(),
            ),
        ]
        .into_iter()
        .collect();

        let response = self
            .session
            .post(url)
            .headers(headers)
            .body(payload)
            .send()
            .await
            .unwrap();
        if response.status().is_redirection() || response.status().is_success() {
            let text = response.text().await.unwrap();
            let captures = Regex::new(r"state=(.*)").unwrap().captures(&text).unwrap();
            let new_state = captures[1].split('"').next().unwrap();
            self.__part_six(state, new_state).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_six(&mut self, old_state: &str, new_state: &str) {
        let url = format!("https://auth0.openai.com/authorize/resume?state={new_state}");
        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "auth0.openai.com".parse().unwrap()),
            (
                header::ACCEPT,
                "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
                    .parse()
                    .unwrap(),
            ),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (header::ACCEPT_LANGUAGE, "en-GB,en-US;q=0.9,en;q=0.8".parse().unwrap()),
            (
                header::REFERER,
                format!("https://auth0.openai.com/u/login/password?state={old_state}")
                    .parse()
                    .unwrap(),
            ),
        ]
        .into_iter()
        .collect();

        let response = self.session.get(&url).headers(headers).send().await.unwrap();
        if response.status().is_redirection() {
            let redirect_url = response.headers().get("location").unwrap();
            self.__part_seven(redirect_url.to_str().unwrap(), &url).await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    async fn __part_seven(&mut self, redirect_url: &str, previous_url: &str) {
        let url = redirect_url;
        let headers: HeaderMap<HeaderValue> = [
            (header::HOST, "explorer.api.openai.com".parse().unwrap()),
            (header::ACCEPT, "application/json".parse().unwrap()),
            (header::CONNECTION, "keep-alive".parse().unwrap()),
            (header::USER_AGENT, self.user_agent.parse().unwrap()),
            (header::ACCEPT_LANGUAGE, "en-GB,en-US;q=0.9,en;q=0.8".parse().unwrap()),
            (header::REFERER, previous_url.parse().unwrap()),
        ]
        .into_iter()
        .collect();

        let response = self.session.get(url).headers(headers).send().await.unwrap();
        if response.status().is_redirection() {
            self.session_token = Some(
                response
                    .cookies()
                    .find(|cookie| cookie.name() == "__Secure-next-auth.session-token")
                    .unwrap()
                    .value()
                    .to_string(),
            );
            self.get_access_token().await;
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }

    pub async fn get_access_token(&mut self) {
        let url = "https://explorer.api.openai.com/api/auth/session".parse().unwrap();
        let cookie = format!(
            "{}={}",
            "__Secure-next-auth.session-token",
            self.session_token.as_ref().unwrap()
        );
        self.cookie_store.add_cookie_str(&cookie, &url);
        let response = self.session.get(url).send().await.unwrap();
        if response.status().is_success() {
            self.access_token = Some(
                response.json::<serde_json::Value>().await.unwrap()["accessToken"]
                    .as_str()
                    .unwrap()
                    .to_string(),
            );
        }
        else {
            panic!("Error: {}", response.text().await.unwrap());
        }
    }
}