├── requirements.txt ├── .gitignore ├── License ├── README.md └── server.py /requirements.txt: -------------------------------------------------------------------------------- 1 | flask 2 | playwright -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 30 | __pypackages__/ 31 | 32 | # Environments 33 | .env 34 | .venv 35 | env/ 36 | venv/ 37 | pyenv/ 38 | ENV/ 39 | env.bak/ 40 | venv.bak/ 41 | 42 | # pytype static type analyzer 43 | .pytype/ 44 | 45 | # Cython debug symbols 46 | cython_debug/ 47 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Taranjeet Singh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatGPT api 2 | 3 | * It uses playwright and chromium to open browser and parse html. 4 | * It is an unoffical api for development purpose only. 5 | 6 | 7 | # How to install 8 | 9 | * Make sure that python and virual environment is installed. 10 | 11 | * Create a new virtual environment 12 | 13 | ```python 14 | # one time 15 | virtualenv -p $(which python3) pyenv 16 | 17 | # everytime you want to run the server 18 | source pyenv/bin/activate 19 | ``` 20 | 21 | * Now install the requirements 22 | 23 | ``` 24 | pip install -r requirements.txt 25 | ``` 26 | 27 | * If you are installing playwright for the first time, it will ask you to run this command for one time only. 28 | 29 | ``` 30 | playwright install 31 | ``` 32 | 33 | * Now run the server 34 | 35 | ``` 36 | python server.py 37 | ``` 38 | 39 | * The server runs at port `5001`. If you want to change, you can change it in server.py 40 | 41 | 42 | # Api Documentation 43 | 44 | * There is a single end point only. It is available at `/chat` 45 | 46 | ```sh 47 | curl -XGET http://localhost:5001/chat?q=Write%20a%20python%20program%20to%20reverse%20a%20list 48 | ``` 49 | 50 | # Updates 51 | 52 | * [8 Dec 2022]: Updated parsing logic (credits @CoolLoong) 53 | 54 | # Credit 55 | 56 | * All the credit for this script goes to [Daniel Gross's whatsapp gpt](https://github.com/danielgross/whatsapp-gpt) package. I have just taken the script as an individual file and added documentation for how to install and run it. 57 | 58 | # Disclaimer 59 | 60 | Please note that this project is a personal undertaking and not an official OpenAI product. It is not affiliated with OpenAI in any way, and should not be mistaken as such. 61 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding:utf-8 -*- 3 | 4 | 5 | import os 6 | import time 7 | import flask 8 | from flask import request 9 | from playwright.sync_api import sync_playwright 10 | 11 | app = flask.Flask(__name__) 12 | playwright = sync_playwright().start() 13 | browser = playwright.chromium.launch_persistent_context(user_data_dir="/tmp/playwright", headless=False) 14 | page = browser.new_page() 15 | 16 | 17 | def get_input_box(): 18 | """Get the child textarea of `PromptTextarea__TextareaWrapper`""" 19 | return page.query_selector("textarea") 20 | 21 | 22 | def is_logged_in(): 23 | # See if we have a textarea with data-id="root" 24 | return get_input_box() is not None 25 | 26 | 27 | def is_loading_response() -> bool: 28 | """See if the send button is disabled, if it is, we're still loading""" 29 | return not page.query_selector("textarea ~ button").is_enabled() 30 | 31 | 32 | def send_message(message): 33 | # Send the message 34 | box = get_input_box() 35 | box.click() 36 | box.fill(message) 37 | box.press("Enter") 38 | 39 | 40 | def get_last_message(): 41 | """Get the latest message""" 42 | while is_loading_response(): 43 | time.sleep(0.25) 44 | page_elements = page.query_selector_all("div[class*='request-:']") 45 | last_element = page_elements[-1] 46 | return last_element.inner_text() 47 | 48 | 49 | def regenerate_response(): 50 | """Clicks on the Try again button. 51 | Returns None if there is no button""" 52 | try_again_button = page.query_selector("button:has-text('Try again')") 53 | if try_again_button is not None: 54 | try_again_button.click() 55 | time.sleep(0.5) 56 | return try_again_button 57 | 58 | 59 | def get_reset_button(): 60 | """Returns the reset thread button (it is an a tag not a button)""" 61 | return page.query_selector("a:has-text('Reset chat')") 62 | 63 | 64 | @app.route("/chat", methods=["POST"]) 65 | def chat(): 66 | message = request.form.get("message") 67 | send_message(message) 68 | response = get_last_message() 69 | return response 70 | 71 | 72 | @app.route("/regenerate", methods=["POST"]) 73 | def regenerate(): 74 | if regenerate_response() is None: 75 | return "No response to regenerate" 76 | response = get_last_message() 77 | return response 78 | 79 | 80 | @app.route("/reset", methods=["POST"]) 81 | def reset(): 82 | get_reset_button().click() 83 | return "Chat thread reset" 84 | 85 | 86 | @app.route("/restart", methods=["POST"]) 87 | def restart(): 88 | global page, browser, playwright 89 | page.close() 90 | browser.close() 91 | playwright.stop() 92 | time.sleep(0.25) 93 | playwright = sync_playwright().start() 94 | browser = playwright.chromium.launch_persistent_context(user_data_dir="/tmp/playwright", headless=False) 95 | page = browser.new_page() 96 | page.goto("https://chat.openai.com/") 97 | return "API restart!" 98 | 99 | 100 | def start_browser(): 101 | page.goto("https://chat.openai.com/") 102 | if not is_logged_in(): 103 | print("Please log in to OpenAI Chat") 104 | print("Press enter when you're done") 105 | input() 106 | else: 107 | print("Logged in") 108 | 109 | app.run(port=5001, threaded=False, host="0.0.0.0") 110 | 111 | 112 | if __name__ == "__main__": 113 | start_browser() 114 | --------------------------------------------------------------------------------