Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

Commit

Permalink
Use undetected_chromedriver
Browse files Browse the repository at this point in the history
  • Loading branch information
terry3041 committed Dec 12, 2022
1 parent cc7884d commit 6ae287b
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 95 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ An unofficial Python wrapper for OpenAI's ChatGPT API

## Getting Started

> On 2022/12/11, OpenAI has implemented Cloudflare's anti-bot protection on the ChatGPT API. This wrapper is now using `undetected_chromedriver` to bypass the protection. **Please make sure you have [Google Chrome](https://www.google.com/chrome/) before using this wrapper.** Authorization using login credentials is removed due to the consistent captcha requirement on the login page.
### Installation

```bash
Expand Down Expand Up @@ -37,13 +39,13 @@ from pyChatGPT import ChatGPT

session_token = 'abc123' # `__Secure-next-auth.session-token` cookie from https://chat.openai.com/chat
api = ChatGPT(session_token) # auth with session token
api2 = ChatGPT(email='[email protected]', password='password') # auth with email and password
api3 = ChatGPT(session_token, conversation_id='some-random-uuid', parent_id='another-random-uuid') # specify a conversation
api4 = ChatGPT(session_token, proxy='http://proxy.example.com:8080') # specify proxy
api2 = ChatGPT(session_token, conversation_id='some-random-uuid', parent_id='another-random-uuid') # specify a conversation
api3 = ChatGPT(session_token, proxy='http://proxy.example.com:8080') # specify proxy

resp = api.send_message('Hello, world!')
print(resp['message'])

api.refresh_cookies() # refresh the cookies (if cloudflare bypass is not working
api.refresh_auth() # refresh the authorization token
api.reset_conversation() # reset the conversation
```
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "pyChatGPT"
version = "0.1.2"
version = "0.2.0"
authors = [
{ name="terry3041", email="[email protected]" },
]
Expand All @@ -17,6 +17,7 @@ classifiers = [
"Operating System :: OS Independent",
]
dependencies = [
'undetected-chromedriver >= 3.1.7',
'requests',
'uuid',
]
Expand Down
120 changes: 29 additions & 91 deletions src/pyChatGPT/pyChatGPT.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.common.by import By

import undetected_chromedriver as uc
import requests
import uuid
import json
import re


class ChatGPT:
Expand All @@ -11,9 +15,7 @@ class ChatGPT:

def __init__(
self,
session_token: str = None,
email: str = None,
password: str = None,
session_token: str,
conversation_id: str = None,
parent_id: str = None,
proxy: str = None,
Expand All @@ -22,9 +24,7 @@ def __init__(
Initialize the ChatGPT class\n
Either provide a session token or email and password\n
Parameters:
- session_token: (optional) Your session token in cookies named as `__Secure-next-auth.session-token` from https://chat.openai.com/chat
- email: (optional) Your OpenAI email
- password: (optional) Your OpenAI password
- session_token: Your session token in cookies named as `__Secure-next-auth.session-token` from https://chat.openai.com/chat
- conversation_id: (optional) The conversation ID if you want to continue a conversation
- parent_id: (optional) The parent ID if you want to continue a conversation
- proxy: (optional) The proxy to use, in URL format (i.e. `https://ip:port`)
Expand All @@ -34,113 +34,51 @@ def __init__(
self.parent_id = parent_id
else:
self.parent_id = str(uuid.uuid4())

self.proxy = proxy
self.proxies = {'http': proxy, 'https': proxy} if proxy else {}
self.headers = {
'accept': '*/*',
'accept-encoding': 'gzip, deflate',
'accept-language': 'en-US;q=0.9,en;q=0.8',
'origin': 'https://chat.openai.com',
'referer': 'https://chat.openai.com/chat',
'sec-ch-ua': '"Not?A_Brand";v="8", "Chromium";v="108", "Microsoft Edge";v="108"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46',
'x-openai-assistant-app-id': '',
}
self.session = requests.Session()
self.session.headers = self.headers
self.session.proxies = self.proxies

self.session_token = session_token
if not self.session_token:
if not email or not password:
raise ValueError('No session token or login credentials are provideddd')
self._login(email, password)
else:
self.headers[
'Cookie'
] = f'__Secure-next-auth.session-token={self.session_token}'
self.refresh_auth()
self.refresh_cookies()

def _login(self, email: str, password: str) -> str:
def refresh_cookies(self) -> None:
'''
Login to OpenAI\n
Parameters:
- email: Your OpenAI email
- password: Your OpenAI password\n
Returns the session token
Refresh the session cookies
'''
self.session.headers = self.headers
self.session.proxies = self.proxies

# Get the CSRF token
resp = self.session.get('https://chat.openai.com/api/auth/csrf')
if resp.status_code != 200:
raise ValueError(f'Status code {resp.status_code}: {resp.text}')
csrf_token = resp.json()['csrfToken']

# Get state
resp = self.session.post(
'https://chat.openai.com/api/auth/signin/auth0?prompt=login',
data={'callbackUrl': '/', 'csrfToken': csrf_token, 'json': 'true'},
options = uc.ChromeOptions()
if self.proxy:
options.add_argument(f'--proxy-server={self.proxy}')
self.driver = uc.Chrome(options=options)

self.driver.get('https://chat.openai.com/')
self.headers['user-agent'] = self.driver.execute_script(
'return navigator.userAgent'
)
if resp.status_code != 200:
raise ValueError(f'Status code {resp.status_code}: {resp.text}')
redirect_url = resp.json()['url']

# Redirect to auth0 /login/identifier
resp = self.session.get(redirect_url)
if resp.status_code != 200:
raise ValueError(f'Status code {resp.status_code}: {resp.text}')
if '<img alt="captcha"' in resp.text:
raise ValueError('Captcha detected')
pattern = r'<input type="hidden" name="state" value="(.*)" \/>'
results = re.findall(pattern, resp.text)
if not results:
raise ValueError(f'Could not get state: {resp.text}')
state = results[0]

# Post email
resp = self.session.post(
f'https://auth0.openai.com/u/login/identifier?state={state}',
data={
'state': state,
'username': email,
'js-available': 'false',
'webauthn-available': 'true',
'is-brave': 'false',
'webauthn-platform-available': 'false',
'action': 'default',
},
self.driver.add_cookie(
{'name': '__Secure-next-auth.session-token', 'value': self.session_token}
)
if resp.status_code != 200:
raise ValueError(f'Status code {resp.status_code}: {resp.text}')

# Post password
resp = self.session.post(
f'https://auth0.openai.com/u/login/password?state={state}',
data={
'state': state,
'username': email,
'password': password,
'action': 'default',
},
WebDriverWait(self.driver, 10).until(
EC.text_to_be_present_in_element((By.TAG_NAME, 'h1'), 'ChatGPT')
)
if resp.status_code != 200:
raise ValueError(f'Status code {resp.status_code}: {resp.text}')

# Get session token in CookieJar
cookies = self.session.cookies.get_dict()
if '__Secure-next-auth.session-token' not in cookies:
raise ValueError(f'Could not get session token: {cookies}')
return cookies['__Secure-next-auth.session-token']
for cookie in self.driver.get_cookies():
self.session.cookies.set(cookie['name'], cookie['value'])
self.driver.close()
self.driver.quit()

def refresh_auth(self) -> None:
'''
Refresh the authorization token
Refresh the session's authorization
'''
resp = self.session.get('https://chat.openai.com/api/auth/session')
if resp.status_code != 200:
Expand Down

0 comments on commit 6ae287b

Please sign in to comment.