Skip to content

Commit 8604d49

Browse files
committed
Init commit
1 parent 617bf45 commit 8604d49

File tree

7 files changed

+332
-0
lines changed

7 files changed

+332
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
env
2+
__pycache__

auth.py

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import os
2+
import base64
3+
import pickle
4+
import logging
5+
from bs4 import BeautifulSoup
6+
7+
8+
class Auth:
9+
CAPTCHA_FILENAME = "captcha.svg"
10+
COOKIES_FILENAME = "cookies.pk"
11+
12+
def __init__(self, username, password):
13+
self.username = username
14+
self.password = password
15+
16+
@classmethod
17+
def get_token(cls, session):
18+
req = session.get("https://chat.openai.com/api/auth/session")
19+
return req.json().get("accessToken")
20+
21+
@classmethod
22+
def save_cookies(cls, session):
23+
with open(cls.COOKIES_FILENAME, "wb") as f:
24+
pickle.dump(session.cookies.jar._cookies, f)
25+
26+
@classmethod
27+
def load_cookies(cls, cookies):
28+
if not os.path.isfile(cls.COOKIES_FILENAME):
29+
return None
30+
with open(cls.COOKIES_FILENAME, "rb") as f:
31+
jar_cookies = pickle.load(f)
32+
for domain, pc in jar_cookies.items():
33+
for path, c in pc.items():
34+
for k, v in c.items():
35+
cookies.set(k, v.value, domain=domain, path=path)
36+
return cookies
37+
38+
@classmethod
39+
def check_auth(cls, session):
40+
token = cls.get_token(session)
41+
return token
42+
43+
def _get_state(self, resp):
44+
return resp.find("input", {"name": "state", "type": "hidden"}).get("value")
45+
46+
def _captcha_handler(self, resp):
47+
captcha = resp.find("img", {"alt": "captcha"})
48+
if captcha:
49+
src = captcha.get("src").split(",")[1]
50+
with open(self.CAPTCHA_FILENAME, "wb") as fh:
51+
fh.write(base64.urlsafe_b64decode(src))
52+
logging.info("Please enter captcha code: ")
53+
code = input()
54+
if os.path.isfile(self.CAPTCHA_FILENAME):
55+
os.remove(self.CAPTCHA_FILENAME)
56+
return code
57+
58+
def _block_handler(self, resp):
59+
s = "We have detected a potential security issue with this account"
60+
return s in str(resp)
61+
62+
def perform_login(self, session):
63+
req = session.get("https://chat.openai.com/api/auth/csrf")
64+
csrf = req.json().get("csrfToken")
65+
66+
session.headers.update({"content-type": "application/x-www-form-urlencoded"})
67+
params = {
68+
"callbackUrl": "/",
69+
"csrfToken": csrf,
70+
"json": "true"
71+
}
72+
req = session.post(
73+
"https://chat.openai.com/api/auth/signin/auth0?prompt=login", data=params
74+
)
75+
login_link = req.json().get("url")
76+
77+
req = session.get(login_link)
78+
response = BeautifulSoup(req.text, features="html.parser")
79+
80+
while True: # try to auth
81+
state = self._get_state(response)
82+
code = self._captcha_handler(response)
83+
84+
session.headers.update({
85+
"content-type": "application/x-www-form-urlencoded"
86+
})
87+
params = {
88+
"state": state,
89+
"username": self.username,
90+
"captcha": code,
91+
"js-available": "true",
92+
"webauthn-available": "true",
93+
"is-brave": "false",
94+
"webauthn-platform-available": "false",
95+
"action": "default"
96+
}
97+
url = f"https://auth0.openai.com/u/login/identifier?state={state}"
98+
req = session.post(url, data=params)
99+
response = BeautifulSoup(req.text, features="html.parser")
100+
101+
# break if password field is found on page
102+
if response.find("input", {"name": "password", "id": "password"}):
103+
break
104+
logging.info("Incorrect captcha or another error, trying again...")
105+
106+
state = self._get_state(response)
107+
108+
is_blocked = self._block_handler(response)
109+
110+
if is_blocked:
111+
logging.warning("We are blocked")
112+
return
113+
114+
req = session.get(f"https://auth0.openai.com/u/login/password?state={state}")
115+
response = BeautifulSoup(req.text, features="html.parser")
116+
state = self._get_state(response)
117+
118+
session.headers.update({
119+
"content-type": "application/x-www-form-urlencoded"
120+
})
121+
params = {
122+
"state": state,
123+
"username": self.username,
124+
"password": self.password,
125+
"action": "default"
126+
}
127+
url = f"https://auth0.openai.com/u/login/password?state={state}"
128+
req = session.post(url, data=params)
129+
return req.url == "https://chat.openai.com/chat"

config.py

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
initial_headers = {
2+
"accept": "*/*",
3+
"accept-encoding": "gzip, deflate, br",
4+
"accept-language": "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,la;q=0.6",
5+
"cache-control": "no-cache",
6+
"content-type": "application/x-www-form-urlencoded",
7+
"pragma": "no-cache",
8+
"referer": "https://chat.openai.com/auth/login",
9+
"sec-ch-ua": '"Not?A_Brand";v="8", "Chromium";v="108", "Google Chrome";v="108"',
10+
"sec-ch-ua-mobile": "?0",
11+
"sec-ch-ua-platform": '"Windows"',
12+
"sec-fetch-dest": "empty",
13+
"sec-fetch-mode": "cors",
14+
"sec-fetch-site": "same-origin",
15+
"user-agent": (
16+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
17+
"(KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
18+
)
19+
}
20+
model = "text-davinci-002-render"

errors.py

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class RequestError(Exception):
2+
"Raised when a request wasn't successfull"
3+
4+
def __init__(self, message=None):
5+
super().__init__(message)
6+
7+
8+
class ResponseError(Exception):
9+
"Raised when the response wasn't successfull"
10+
11+
def __init__(self, message=None):
12+
super().__init__(message)

main.py

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import httpx
2+
import logging
3+
import argparse
4+
5+
import config
6+
from auth import Auth
7+
from stream import Stream
8+
9+
parser = argparse.ArgumentParser(description="ChatGPT dialog without browser")
10+
parser.add_argument(
11+
"-u",
12+
"--username",
13+
dest="username",
14+
help="Your username (email) for openai.com",
15+
required=True,
16+
)
17+
parser.add_argument(
18+
"-p",
19+
"--password",
20+
dest="password",
21+
help="Your password for openai.com",
22+
required=True,
23+
)
24+
parser.add_argument(
25+
"-m",
26+
"--message",
27+
dest="message",
28+
help="Message for ChatGPT",
29+
required=True,
30+
)
31+
parser.add_argument(
32+
"-vvv",
33+
"--verbose",
34+
action="store_true",
35+
dest="verbose",
36+
help="Enable debug logging level",
37+
)
38+
39+
args = parser.parse_args()
40+
41+
42+
level = (logging.DEBUG if args.verbose else logging.INFO)
43+
logging.basicConfig(level=level)
44+
45+
46+
def log_request(req):
47+
logging.debug(f"Request event hook: {req.method} {req.url}")
48+
49+
50+
def log_response(resp):
51+
req = resp.request
52+
logging.debug(f"Response event hook: {req.method} {req.url}")
53+
logging.debug(f"Status - {resp.status_code}")
54+
logging.debug(resp.read())
55+
for cookie in resp.cookies:
56+
logging.debug("Cookie", cookie, resp.cookies[cookie])
57+
for header in resp.headers:
58+
logging.debug("Header", header, resp.headers[header])
59+
60+
61+
if args.verbose:
62+
event_hooks = {"request": [log_request], "response": [log_response]}
63+
else:
64+
event_hooks = None
65+
66+
session = httpx.Client(
67+
http2=True,
68+
headers=config.initial_headers,
69+
event_hooks=event_hooks,
70+
follow_redirects=True,
71+
cookies=Auth.load_cookies(httpx.Cookies()),
72+
)
73+
session.cookies.jar.clear_expired_cookies()
74+
75+
while not Auth.check_auth(session):
76+
logging.info("Start the loging process")
77+
auth_instance = Auth(args.username, args.password)
78+
auth_instance.perform_login(session)
79+
80+
81+
stream = Stream(session)
82+
response = stream.send_message(args.message)
83+
print(response)

requirements.txt

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
anyio==3.6.2
2+
beautifulsoup4==4.11.1
3+
certifi==2022.12.7
4+
cffi==1.15.1
5+
charset-normalizer==2.1.1
6+
cryptography==38.0.4
7+
h11==0.14.0
8+
h2==4.1.0
9+
hpack==4.0.0
10+
httpcore==0.16.2
11+
httpx==0.23.1
12+
hyperframe==6.0.1
13+
idna==3.4
14+
ndg-httpsclient==0.5.1
15+
pyasn1==0.4.8
16+
pycparser==2.21
17+
pyOpenSSL==22.1.0
18+
requests==2.28.1
19+
rfc3986==1.5.0
20+
sniffio==1.3.0
21+
soupsieve==2.3.2.post1
22+
urllib3==1.26.13

stream.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import uuid
2+
import json
3+
4+
import config
5+
from auth import Auth
6+
from errors import ResponseError
7+
8+
9+
class Stream:
10+
def __init__(self, session):
11+
self.session = session
12+
13+
def _process_chunks(self, chunks):
14+
if type(chunks) == bytes:
15+
chunks = chunks.decode()
16+
chunks = chunks.split("\n\n")
17+
elif type(chunks) == list:
18+
chunks = [c.decode() for c in chunks]
19+
else:
20+
raise ResponseError("Unknown response")
21+
chunks = [c for c in chunks if c and "message" in c]
22+
if not chunks:
23+
raise ResponseError("No response in stream")
24+
chunk = chunks[-1]
25+
chunk = chunk.replace("data: ", "").strip()
26+
chunk = json.loads(chunk)
27+
return chunk.get("message", {}).get("content", {}).get("parts", [None])[0]
28+
29+
def send_message(self, message):
30+
token = Auth.get_token(self.session)
31+
32+
self.session.headers.update({
33+
"accept": "text/event-stream",
34+
"X-OpenAI-Assistant-App-Id": "",
35+
"Authorization": f"Bearer {token}"
36+
})
37+
38+
params = {
39+
"action": "next",
40+
"messages": [
41+
{
42+
"id": str(uuid.uuid4()),
43+
"role": "user",
44+
"content": {
45+
"content_type": "text",
46+
"parts": [message]
47+
}
48+
}
49+
],
50+
"parent_message_id": "",
51+
"model": config.model
52+
}
53+
54+
with self.session.stream(
55+
"POST", "https://chat.openai.com/backend-api/conversation",
56+
json=params, headers={"content-type": "application/json"},
57+
timeout=None
58+
) as response:
59+
chunks = []
60+
for chunk in response.iter_bytes():
61+
chunks.append(chunk)
62+
63+
response = self._process_chunks(chunks)
64+
return response

0 commit comments

Comments
 (0)