├── .github └── FUNDING.yml ├── LICENSE ├── README.md ├── access_tokens.json ├── api ├── ChatGPT_Server.py ├── static │ └── image │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── logo.png │ │ └── site.webmanifest └── templates │ ├── access-token.html │ ├── admin │ └── add-user.html │ ├── index.html │ └── recall.html ├── classes ├── ChatGPT.py ├── ChatGPTClient.py └── ChatGPTServer.py ├── contrib └── OpenAIAuth │ ├── Cloudflare.py │ └── OpenAIAuth.py ├── conversations.json ├── forms ├── FormAccessToken.py ├── FormAddUser.py ├── FormChatGPT.py └── FormChatRecall.py ├── helpers └── General.py ├── htmls └── Blocks.py ├── localizations └── en-us.json ├── main.py ├── requirements.txt ├── settings.json ├── settings └── Settings.py ├── shinysocks ├── README.md ├── shinysocks.conf └── shinysocks.exe └── users.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ['Lukium'] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: Lukium # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://lukium.com/tip'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Lukium (https://github.com/Lukium) 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-Server 2 | ChatGPT Web API server Flask that can respond both via browser through Flask Forms and directly via POST/GET Requests 3 | 4 | ## Features: 5 | ### Multi Option Accessibility: 6 | - POST/GET request for programmatic access 7 | - WebUI browser access 8 | ### Flexibility: 9 | - Replies can be either the full JSON or just the cut down reply 10 | - Endpoints can be easily customized (changing the path) via settings file without any coding understanding needed 11 | ### Basic Permissions: 12 | - Admin users can create (and soon remove) Client users or regular users 13 | - Client users can create (and soon remove) regular users that it created 14 | - Users can only use ChatGPT functionality. 15 | - Users can be given access (or denied) to Plus ChatGPT accounts separately from free accounts. 16 | ### Account Support: 17 | - Multi/Single built in ChatGPT Accounts, including Pro/Free accounts: accounts will be cycled on each request (follow ups will use the original account to keep conversation integrity) 18 | - User account via Access Token (which can be retrieved via dedicated endpoint via email/password) 19 | ### Message Handling: 20 | - In addition to the JSON from the ChatGPT reply, the API will also add additional information to the JSON, including the original prompt, the datetime the query was initiated; if a followup, the last message's prompt and reply, as well as a series of other useful information. 21 | - All new conversations have a title automatically generated, which is also added to the JSON reply. 22 | ### User Conversation caching and followup: 23 | - All user conversations are cached to the specific user. 24 | - Users can recall either all conversations along with their titles or a single conversation including all messages within the conversation (both prompt and reply) sorted chronologically. 25 | - When a conversation is followed up, the server will automatically use the account that was used to initiate the conversation, and use the last message in that conversation for the parent id, so as to maintain perfect conversation integrity. If the account used originally is not available, the user will receive an error. 26 | - Conversations can be removed from the cache using the `remove-conversation` endpoint 27 | ### Client Support: 28 | - "Client" users can be created by the admin, which can programmatically create API keys when necessary. For example: say you wanted to create a Discord Bot that interacts with the API. You could issue it a Client API key, and the bot will be able to automatically generate API keys for users that the bot has determined should be able to access the API. 29 | - After generating an API key for its user, the Client can refer to its user via its own `userid` for ease of use (without having to use the API key) 30 | ### WebUI: 31 | - WebUI can be used for all functionality, including chat, followup, access-token retrieval, API Key issuing. 32 | - WebUI replies can be set to use markdown so they look cleaner. 33 | - WebUI can also reply in pure JSON 34 | 35 | ## FOR EXPERIMENTAL AND LEARNING USE ONLY: 36 | - I came up with this project while thinking of a use case where OpenAI, when providing its capabilities for an organization, say for example a University and its employees/students, rather than providing individual accounts that are managed by OpenAI, might instead provide a single account for the entire organization, such that the organization can then manage its own users via an API like the one I developed. 37 | 38 | - In this scenario, the individual's API key might be passed via the API to OpenAI for moderation and user counting, such that in turn, OpenAI can charge the organization based on the number of users utilizing the API via the org account. In this use case, the overall amount of resources used for user management is drastically reduced, since the organization would already have user records for each individual, which in my opinion, makes having OpenAI have a duplicate of records on its side redundant and generally a waste of resources. As a result, this setup would make the process more efficient for OpenAI in such a use case, reducing the resource usage for user authentication (one account vs potentially thousands), message storage (offloading it to the organization's server), and user billing (billing a single account instead of potentially thousands). 39 | 40 | - For the Organization, this makes it further beneficial, as it provides it with more granular control of ChatGPT usage, including the ability to check if a user might have committed plagiarism, since it would, in case of a suspicion, be able to verify the user's message cache. Furthermore, OpenAI, having saved in resources, might be able to pass on some of those savings to the org in the way of cheaper per/user cost, making it a win for both sides. 41 | 42 | ### To use: 43 | 1. Install everything in requirements.txt using `pip install -r ./requirements.txt` 44 | 45 | 2. Make necessary modifications to `settings.json` file: 46 | 47 | In `["OpenAI"]["instances"]` add at least one account to be used as a built in account. You can add as many as you like by just adding additional keys. Each instance will be cycled through via the server. 48 | Each instance can also have it's own proxy by adding a `proxy` key. Proxies should be socks5 valid strings like `socks5://127.0.0.1:1080` 49 | 50 | Modify the `["api_server"]` key to match your needs: 51 | 52 | `"host"` `string` is the interface where your API should be accessible 53 | 54 | `"port"` `int` is the port where the API should be accessible 55 | 56 | `"default_proxy"` `string` is the default proxy to be used, and should be a valid socks5 string like `socks5://127.0.0.1:1080` 57 | 58 | Included in the folder /shinysocks is a basic windows socks5 server (see credits for more info) 59 | If running in linux, I recommend using `microsocks` https://github.com/rofl0r/microsocks 60 | 61 | `"wan_url"` `string` is the url the API will be accessible through if one has been setup (in dev) 62 | 63 | `"local_or_wan"` `string` is whether the API should be accessible via WAN or just LAN (should be `wan` or `lan`) (in dev) 64 | 65 | 66 | 3. Run `main.py` using `python ./main.py` (or whatever method available to you to run python scripts) 67 | This will create admin keys in your settings.json at first run, please make sure to keep these safe as they will have the power to add/remove users, access all cached conversations, etc. 68 | 69 | 4. Access the API by using either GET or POST request. You can also make the request via browser with the following format: 70 | The API has browser forms accessible via `/chat` endpoint for using ChatGPT, `/access-token` for retrieving access tokens and `/admin/add-user` for adding users 71 | The API has direct endpoints via `/api/chat` and `/api/access-token` for doing the same functions via POST/GET requests (add-user coming soon) 72 | 73 | Users can only use the API if they have been issued an API Key via `/admin/add-user` 74 | 75 | The endpoint will create a user in `./users.json` that include their API Key, username, userID, and whether they have access to plus builtin instances or not (as well as other future permissions) 76 | 77 | The following arguments are available for chat endpoints: 78 | 79 | `user`: (`API KEY`, default `none`) User's API Key (Not OpenAI but the one issued via `/admin/add-user`) 80 | 81 | `plus`: (`true` | `false` | `any`, default `false`) Will dictate whether to use a ChatGPT plus instance during the request. If `true` or `any` (first available) is passed, a plus instance may be attempted and if the user doesn't have `plus` access in their credentials an exception may be thrown, therefore, users without access to plus should use `false` 82 | 83 | `prompt`: (`prompt test`, default: `prompt that lets user know they left it blank`) The prompt to be passed to ChatGPT 84 | 85 | `reply_only`: (`true` | `false`, default: `true` on browser, `false` on `/api`) True will return just the ChatGPT text reply, rather than json with all information 86 | 87 | `pretty`: (`true` | `false`, default: `true` on browser, `false` on `/api`) Only effective if `reply_only` = `true` Will determine whether to run the reply through markdown for better looking reply, especially useful for when using the API via the browser 88 | 89 | `access_token`: (`OpenAI Access Token`, default: None) When passed (This will essentially override `plus` with the `user_plus` parameter, since it will not be using a builtin instance), the API will create an instance of the `ChatGPT` class specifically using the access token to make a ChatGPT request using the account associated with the access token rather than one of the accounts setup in `settings.json`. Access tokens can be retrieved using the `/access-token` or `/api/access-token` endpoints. 90 | 91 | `user_plus`: (`true` | `false`, default: `false`) Whether the account linked to the access_token has access to ChatGPT Plus or not. Will determine which model to use, which may throw an exception if set to true when the user does not have access to to Plus 92 | 93 | ### Credits: 94 | https://github.com/acheong08/OpenAIAuth OpenAIAuth - several modifications have been made to add functionality to the API 95 | 96 | https://github.com/acheong08/ChatGPT-Proxy - Basic information on how to process prompts to ChatGPT 97 | 98 | https://github.com/jgaa/shinysocks - Basic Windows Proxy server 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /access_tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "accounts": { 3 | 4 | } 5 | } -------------------------------------------------------------------------------- /api/ChatGPT_Server.py: -------------------------------------------------------------------------------- 1 | #IMPORT BUILT-IN LIBRARIES 2 | import asyncio 3 | import json 4 | 5 | #IMPORT THIRD-PARTY LIBRARIES 6 | from flask import Flask 7 | from flask import jsonify 8 | from flask import request 9 | from flask import render_template, redirect, url_for 10 | from flask_bootstrap import Bootstrap 11 | 12 | #IMPORT CLASSES 13 | from classes.ChatGPTServer import ChatGPTServer 14 | 15 | #IMPORT FORMS 16 | from forms.FormChatGPT import FormChatGPT 17 | from forms.FormAddUser import FormAddUser 18 | from forms.FormAccessToken import FormAccessToken 19 | from forms.FormChatRecall import FormChatRecall 20 | 21 | #IMPORT SETTINGS 22 | import settings.Settings as Settings 23 | from settings.Settings import LANG 24 | 25 | #SETUP FLASK APP 26 | app = Flask(__name__) 27 | app.config['SECRET_KEY'] = Settings.API_APP_SECRET 28 | Bootstrap(app) 29 | 30 | ChatGPTServer = asyncio.run(ChatGPTServer.create()) 31 | 32 | #SETUP ROUTES 33 | @app.route(Settings.ENDPOINT_API_CHATGPT, methods=["GET", "POST"]) 34 | async def api_chat(): 35 | response: dict = {} 36 | 37 | if request.method == "GET": 38 | prompt = request.args.get("prompt", Settings.API_DEFAULT_PROMPT) 39 | client = request.args.get("client", None) 40 | user = request.args.get("user", None) 41 | user_id = request.args.get("user_id", None) 42 | username = request.args.get("username", None) 43 | user_id_plus = request.args.get("user_id_plus", None) 44 | conversation_id = request.args.get("conversation_id", None) 45 | plus = request.args.get("plus", "false") 46 | reply_only = request.args.get("reply_only", "false") 47 | pretty = request.args.get("pretty", "false") 48 | access_token = request.args.get("access_token", None) 49 | user_plus = request.args.get("user_plus", "false") 50 | elif request.method == "POST": 51 | prompt = request.json.get("prompt", Settings.API_DEFAULT_PROMPT) 52 | client = request.json.get("client", None) 53 | user = request.json.get("user", None) 54 | user_id = request.json.get("user_id", None) 55 | username = request.json.get("username", None) 56 | user_id_plus = request.json.get("user_id_plus", None) 57 | conversation_id = request.json.get("conversation_id", None) 58 | plus = request.json.get("plus", "false") 59 | reply_only = request.json.get("reply_only", "false") 60 | pretty = request.json.get("pretty", "false") 61 | access_token = request.json.get("access_token", None) 62 | user_plus = request.json.get("user_plus", "false") 63 | 64 | Skip_User_Check = False 65 | #Perform Client Check if client is provided 66 | if client is not None: 67 | client_check = await ChatGPTServer.check_client(user=client) 68 | if client_check['status'] == 'error': 69 | return client_check, 400 #Return Error Message if Client is Invalid 70 | else: 71 | if user is None: 72 | if user_id is None or username is None or user_id_plus is None: 73 | response = { 74 | "status": "error", 75 | "message": "[user_id], [username] and [user_id_plus] are required if [client] is provided and [user] is not provided" 76 | } 77 | return response, 400 #Return Error Message if User ID, Username and User ID Plus are not provided 78 | client_user_id_check: dict = await ChatGPTServer.client_user_id_check(client=client, user_id=user_id, username=username, user_id_plus=user_id_plus) 79 | if client_user_id_check['status'] == 'error': 80 | return client_user_id_check, 400 #Return Error Message if Client User ID Check is Invalid 81 | else: 82 | user = client_user_id_check['key'] 83 | Skip_User_Check = True 84 | 85 | 86 | #Perform User Check 87 | if not Skip_User_Check: 88 | user_check = await ChatGPTServer.check_user(user=user) 89 | if user_check['status'] == 'error': 90 | return user_check, 400 #Return Error Message if User is Invalid 91 | 92 | if access_token != "" and access_token is not None: 93 | response: dict = await ChatGPTServer.process_chagpt_request( 94 | client=client, 95 | user=user, 96 | prompt=prompt, 97 | conversation_id=conversation_id, 98 | type='user', 99 | access_token=access_token, 100 | user_plus=user_plus 101 | ) 102 | else: 103 | response: dict = await ChatGPTServer.process_chagpt_request( 104 | client=client, 105 | user=user, 106 | prompt=prompt, 107 | conversation_id=conversation_id, 108 | plus=plus 109 | ) 110 | 111 | if reply_only == "true": 112 | response = await ChatGPTServer.process_reply_only( 113 | response=response, 114 | pretty=pretty 115 | ) 116 | 117 | return response, 200 118 | 119 | @app.route(Settings.ENDPOINT_BROWSER_CHATGPT, methods=["GET", "POST"]) 120 | async def chat(): 121 | form = FormChatGPT() 122 | user = request.args.get("user", None) 123 | access_token = request.args.get("access_token", None) 124 | if user is not None and user != "": 125 | form.api_key.data = user 126 | if access_token is not None: 127 | form.access_token.data = access_token 128 | 129 | title = f'{LANG["htmls"]["index"]["title"]}' 130 | message_under_title = f'{LANG["htmls"]["index"]["message_under_title"]}' 131 | action_instruction = f'{LANG["htmls"]["index"]["action_instruction"]}' 132 | message_after_submit_1 = f'{LANG["htmls"]["index"]["message_after_submit_1"]}' 133 | message_after_submit_2 = f'{LANG["htmls"]["index"]["message_after_submit_2"]}' 134 | 135 | if form.validate_on_submit(): 136 | #Perform User Check 137 | user_check = await ChatGPTServer.check_user(user=form.api_key.data) 138 | if user_check['status'] == 'error': 139 | return user_check, 400 #Return Error Message if User is Invalid 140 | 141 | if form.access_token.data != "": 142 | response: dict = await ChatGPTServer.process_chagpt_request( 143 | user=form.api_key.data, 144 | prompt=form.prompt.data, 145 | conversation_id=form.conversation_id.data, 146 | type='user', 147 | access_token=form.access_token.data, 148 | user_plus=form.user_plus.data 149 | ) 150 | else: 151 | response: dict = await ChatGPTServer.process_chagpt_request( 152 | user=form.api_key.data, 153 | prompt=form.prompt.data, 154 | conversation_id=form.conversation_id.data, 155 | plus=form.plus.data 156 | ) 157 | 158 | if form.reply_only.data == "true": 159 | response = await ChatGPTServer.process_reply_only( 160 | response=response, 161 | pretty=form.pretty.data 162 | ) 163 | 164 | return response, 200 165 | return render_template( 166 | "index.html", 167 | form=form, title=title, 168 | message_under_title=message_under_title, 169 | action_instruction=action_instruction, 170 | message_after_submit_1=message_after_submit_1, 171 | message_after_submit_2=message_after_submit_2 172 | ) 173 | 174 | @app.route(Settings.ENDPOINT_API_CHATRECALL, methods=["GET", "POST"]) 175 | async def api_recall(): 176 | 177 | if request.method == "GET": 178 | client = request.args.get("client", None) 179 | user = request.args.get("user", None) 180 | conversation_id = request.args.get("conversation_id", None) 181 | elif request.method == "POST": 182 | client = request.json.get("client", None) 183 | user = request.json.get("user", None) 184 | conversation_id = request.json.get("conversation_id", None) 185 | 186 | #Perform Client Check if client is provided 187 | if client is not None: 188 | client_check = await ChatGPTServer.check_client(user=client) 189 | if client_check['status'] == 'error': 190 | return client_check, 400 #Return Error Message if Client is Invalid 191 | 192 | #Perform User Check 193 | user_check = await ChatGPTServer.check_user(user=user) 194 | if user_check['status'] == 'error': 195 | return user_check, 400 #Return Error Message if User is Invalid 196 | 197 | if conversation_id is None or conversation_id == "": 198 | response = await ChatGPTServer.recall(client=client, user=user) 199 | else: 200 | response = await ChatGPTServer.recall(client=client, user=user, conversation_id=conversation_id) 201 | 202 | return response, 200 203 | 204 | @app.route(Settings.ENDPOINT_BROWSER_CHATRECALL, methods=["GET", "POST"]) 205 | async def chat_recall(): 206 | form = FormChatRecall() 207 | user = request.args.get("user", None) 208 | if user is not None and user != "": 209 | form.api_key.data = user 210 | 211 | title = f'{LANG["htmls"]["recall"]["title"]}' 212 | message_under_title = f'{LANG["htmls"]["recall"]["message_under_title"]}' 213 | action_instruction = f'{LANG["htmls"]["recall"]["action_instruction"]}' 214 | 215 | if form.validate_on_submit(): 216 | #Perform User Check 217 | user_check = await ChatGPTServer.check_user(user=form.api_key.data) 218 | if user_check['status'] == 'error': 219 | return user_check, 400 #Return Error Message if User is Invalid 220 | 221 | conversation_id = form.conversation_id.data 222 | if conversation_id is None or conversation_id == "": 223 | response = await ChatGPTServer.recall(user=form.api_key.data) 224 | else: 225 | response = await ChatGPTServer.recall(user=form.api_key.data, conversation_id=conversation_id) 226 | 227 | return response, 200 228 | 229 | return render_template( 230 | "recall.html", 231 | form=form, 232 | title=title, 233 | message_under_title=message_under_title, 234 | action_instruction=action_instruction 235 | ) 236 | 237 | @app.route(Settings.ENDPOINT_API_ACCESS_TOKEN, methods=["GET", "POST"]) 238 | async def api_access_token(): 239 | if request.method == 'GET': 240 | user = request.args.get('user', None) 241 | email = request.args.get('email', None) 242 | password = request.args.get('password', None) 243 | elif request.method == 'POST': 244 | user = request.json.get('user', None) 245 | email = request.json.get('email', None) 246 | password = request.json.get('password', None) 247 | 248 | if user is not None and email is not None and password is not None: 249 | user_check = await ChatGPTServer.check_user(user=user) 250 | if user_check['status'] == 'error': 251 | return user_check, 400 #Return Error Message if User is Invalid 252 | 253 | access_token = await ChatGPTServer.retrieve_access_token(email=email, password=password) 254 | if access_token == 'Wrong email or password provided': 255 | response = json.loads(f'{{"Error": "{access_token}"}}') 256 | else: 257 | response = json.loads(f'{{"Access Token": "{access_token}"}}') 258 | return response, 200 259 | elif user is None: 260 | response = json.loads('{"Error": "Please provide a valid user."}') 261 | return response, 400 262 | elif email is None: 263 | response = json.loads('{"Error": "Please provide a valid email."}') 264 | return response, 400 265 | elif password is None: 266 | response = json.loads('{"Error": "Please provide a valid password."}') 267 | return response, 400 268 | 269 | @app.route(Settings.ENDPOINT_BROWSER_ACCESS_TOKEN, methods=["GET", "POST"]) 270 | async def access_token(): 271 | form = FormAccessToken() 272 | user = request.args.get("user", None) 273 | if user is not None and user != "": 274 | form.api_key.data = user 275 | 276 | title = f'{LANG["htmls"]["access_token"]["title"]}' 277 | message_under_title = f'{LANG["htmls"]["access_token"]["message_under_title"]}' 278 | action_instruction = f'{LANG["htmls"]["access_token"]["action_instruction"]}' 279 | message = f'{LANG["htmls"]["access_token"]["message"]}' 280 | 281 | message = f'This will retrieve your current ChatGPT OpenAI Access Token using your ChatGPT Email and Password.' 282 | if form.validate_on_submit(): 283 | user = form.api_key.data 284 | email = form.email.data 285 | password = form.password.data 286 | 287 | #Perform User Check 288 | user_check = await ChatGPTServer.check_user(user=user) 289 | if user_check['status'] == 'error': 290 | return user_check, 400 #Return Error Message if User is Invalid 291 | 292 | access_token = await ChatGPTServer.retrieve_access_token(email=email, password=password) 293 | if access_token == 'Wrong email or password provided': 294 | response = json.loads(f'{{"Error": "{access_token}"}}') 295 | else: 296 | response = json.loads(f'{{"Access Token": "{access_token}"}}') 297 | return response, 200 298 | 299 | return render_template( 300 | "access-token.html", 301 | form=form, 302 | title=title, 303 | message_under_title=message_under_title, 304 | action_instruction=action_instruction, 305 | message=message) 306 | 307 | @app.route(Settings.ENDPOINT_BROWSER_ADD_USER, methods=["GET", "POST"]) 308 | async def add_user(): 309 | response: dict = {} 310 | admin = request.args.get("admin", None) 311 | client = request.args.get("client", None) 312 | 313 | title = f'{LANG["htmls"]["add_user"]["title"]}' 314 | message_under_title = f'{LANG["htmls"]["add_user"]["message_under_title"]}' 315 | action_instruction = f'{LANG["htmls"]["add_user"]["action_instruction"]}' 316 | 317 | if admin is not None and admin != "": 318 | admin_check = await ChatGPTServer.check_admin(user=admin) 319 | if admin_check['status'] == 'error': 320 | return admin_check, 400 #Return Error Message if Admin is Invalid 321 | else: 322 | admin = True 323 | 324 | if client is not None and client != "": 325 | client_check = await ChatGPTServer.check_client(user=client) 326 | if client_check['status'] == 'error': 327 | return client_check, 400 #Return Error Message if Client is Invalid 328 | else: 329 | client = True 330 | 331 | if not admin and not client: 332 | response['status'] = 'error' 333 | response['message'] = 'No valid Admin or Client API Key provided' 334 | return jsonify(response), 400 335 | else: 336 | form = FormAddUser() 337 | if form.validate_on_submit(): 338 | userid = form.userid.data 339 | username = form.username.data 340 | plus = form.plus.data 341 | new_user_is_client = form.is_client.data 342 | 343 | if plus == "true": 344 | plus = True 345 | elif plus == "false": 346 | plus = False 347 | if new_user_is_client == "true": 348 | new_user_is_client = True 349 | elif new_user_is_client == "false": 350 | new_user_is_client = False 351 | 352 | if client: 353 | if new_user_is_client: 354 | response['status'] = 'error' 355 | response['message'] = 'Clients cannot create new clients' 356 | return jsonify(response), 400 357 | 358 | status, message, key = await ChatGPTServer.add_user(userid=userid, username=username, plus=plus, is_client=new_user_is_client) 359 | response['status'] = status 360 | response['message'] = message 361 | response['api_key'] = key 362 | if status == 'error': 363 | return jsonify(response), 422 364 | else: 365 | return jsonify(response), 200 366 | return render_template( 367 | "admin/add-user.html", 368 | title=title, 369 | message_under_title=message_under_title, 370 | action_instruction=action_instruction, 371 | form=form) 372 | 373 | @app.route(Settings.ENDPOINT_API_REMOVE_CONVERSATION, methods=["GET", "POST"]) 374 | async def api_remove_conversation() -> dict: 375 | response: dict = {} 376 | if request.method == 'GET': 377 | admin = request.args.get('admin', None) 378 | client = request.args.get('client', None) 379 | user = request.args.get('user', None) 380 | user_id = request.args.get('user_id', None) 381 | conversation_id = request.args.get('conversation_id', None) 382 | elif request.method == 'POST': 383 | admin = request.json.get('admin', None) 384 | client = request.json.get('client', None) 385 | user = request.json.get('user', None) 386 | user_id = request.json.get('user_id', None) 387 | conversation_id = request.json.get('conversation_id', None) 388 | 389 | is_admin = False 390 | is_client = False 391 | is_user = False 392 | 393 | if admin is not None and admin != "": 394 | admin_check = await ChatGPTServer.check_admin(user=admin) 395 | if admin_check['status'] == 'error': 396 | return admin_check, 400 #Return Error Message if Admin is Invalid 397 | else: 398 | is_admin = True 399 | 400 | 401 | Skip_User_Check = False 402 | #Perform Client Check if client is provided 403 | if client is not None: 404 | client_check = await ChatGPTServer.check_client(user=client) 405 | if client_check['status'] == 'error': 406 | return client_check, 400 #Return Error Message if Client is Invalid 407 | else: 408 | is_client = True 409 | if user is None: 410 | if user_id is None: 411 | response = { 412 | "status": "error", 413 | "message": "[user_id] is required if [client] is provided and [user] is not provided" 414 | } 415 | return response, 400 #Return Error Message if User ID, Username and User ID Plus are not provided 416 | get_user_from_user_id: dict = await ChatGPTServer.get_user_from_user_id(user_id=user_id) 417 | if get_user_from_user_id['status'] == 'error': 418 | return get_user_from_user_id, 400 #Return Error Message if Client User ID Check is Invalid 419 | else: 420 | user = get_user_from_user_id['user'] 421 | is_user = True 422 | Skip_User_Check = True 423 | 424 | if not Skip_User_Check: 425 | if user is not None and user != "": 426 | user_check = await ChatGPTServer.check_user(user=user) 427 | if user_check['status'] == 'error': 428 | return user_check, 400 #Return Error Message if User is Invalid 429 | else: 430 | is_user = True 431 | 432 | if not is_admin and not is_client and not is_user: 433 | response['status'] = 'error' 434 | response['message'] = 'No valid Admin, Client or User provided' 435 | return jsonify(response), 400 436 | else: 437 | response: dict = await ChatGPTServer.remove_conversation(user=user, conversation_id=conversation_id) 438 | return jsonify(response), 200 439 | 440 | 441 | @app.route(Settings.ENDPOINT_API_ADD_USER, methods=["GET", "POST"]) 442 | async def api_add_user(): 443 | response: dict = {} 444 | if request.method == 'GET': 445 | admin = request.args.get('admin', None) 446 | client = request.args.get('client', None) 447 | userid = request.args.get('userid', None) 448 | username = request.args.get('username', None) 449 | plus = request.args.get('plus', None) 450 | new_user_is_client = request.args.get('is_client', None) 451 | elif request.method == 'POST': 452 | admin = request.json.get('admin', None) 453 | client = request.json.get('client', None) 454 | userid = request.json.get('userid', None) 455 | username = request.json.get('username', None) 456 | plus = request.json.get('plus', None) 457 | new_user_is_client = request.json.get('is_client', 'false') 458 | 459 | is_admin = False 460 | is_client = False 461 | if admin is not None and admin != "": 462 | admin_check = await ChatGPTServer.check_admin(user=admin) 463 | if admin_check['status'] == 'error': 464 | return admin_check, 400 #Return Error Message if Admin is Invalid 465 | else: 466 | is_admin = True 467 | 468 | if client is not None and client != "": 469 | client_check = await ChatGPTServer.check_client(user=client) 470 | if client_check['status'] == 'error': 471 | return client_check, 400 #Return Error Message if Client is Invalid 472 | else: 473 | is_client = True 474 | 475 | if not is_admin and not is_client: 476 | response['status'] = 'error' 477 | response['message'] = 'No valid Admin or Client API Key provided' 478 | return jsonify(response), 400 479 | else: 480 | if is_client: 481 | if new_user_is_client == "true": 482 | response['status'] = 'error' 483 | response['message'] = 'Clients cannot create new clients' 484 | return jsonify(response), 400 485 | 486 | if plus == "true": 487 | plus = True 488 | elif plus == "false": 489 | plus = False 490 | 491 | if new_user_is_client == "true": 492 | new_user_is_client = True 493 | elif new_user_is_client == "false": 494 | new_user_is_client = False 495 | 496 | status, message, key = await ChatGPTServer.add_user(client=client, userid=userid, username=username, plus=plus, is_client=new_user_is_client) 497 | response['status'] = status 498 | response['message'] = message 499 | response['api_key'] = key 500 | if status == 'error': 501 | return jsonify(response), 422 502 | else: 503 | return jsonify(response), 200 504 | 505 | #Redirect Root to Browser 506 | @app.route("/", methods=["GET"]) 507 | def root(): 508 | return redirect(url_for('chat')) 509 | 510 | # Load Browser Favorite Icon 511 | @app.route('/android-chrome-192x192.png', methods=["GET"]) 512 | def android_chrome_192(): 513 | return url_for('static', filename='image/android-chrome-192x192.png') 514 | 515 | @app.route('/android-chrome-512x512.png', methods=["GET"]) 516 | def android_chrome_512(): 517 | return url_for('static', filename='image/android-chrome-512x512.png') 518 | 519 | @app.route('/apple-touch-icon.png', methods=["GET"]) 520 | def apple_touch_icon(): 521 | return url_for('static', filename='image/apple-touch-icon.png') 522 | 523 | @app.route('/favicon.ico', methods=["GET"]) 524 | def favicon(): 525 | return url_for('static', filename='image/favicon.ico') 526 | 527 | @app.route('/favicon-16x16.png', methods=["GET"]) 528 | def favicon_16(): 529 | return url_for('static', filename='image/favicon-16x16.png') 530 | 531 | @app.route('/favicon-32x32.png', methods=["GET"]) 532 | def favicon_32(): 533 | return url_for('static', filename='image/favicon-32x32.png') -------------------------------------------------------------------------------- /api/static/image/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/android-chrome-192x192.png -------------------------------------------------------------------------------- /api/static/image/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/android-chrome-512x512.png -------------------------------------------------------------------------------- /api/static/image/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/apple-touch-icon.png -------------------------------------------------------------------------------- /api/static/image/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/favicon-16x16.png -------------------------------------------------------------------------------- /api/static/image/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/favicon-32x32.png -------------------------------------------------------------------------------- /api/static/image/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/favicon.ico -------------------------------------------------------------------------------- /api/static/image/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/api/static/image/logo.png -------------------------------------------------------------------------------- /api/static/image/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /api/templates/access-token.html: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap/base.html' %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block styles %} 5 | {{ super() }} 6 | 10 | {% endblock %} 11 | 12 | 13 | {% block title %} 14 | Lukium Swarm - ChatGPT API 15 | {% endblock %} 16 | 17 | 18 | {% block content %} 19 | 20 |
21 |
22 |
23 | 24 |

{{ title }}

25 | {{ message_under_title }}

26 | 27 |

{{ action_instruction }}

28 |

{{ message }}

29 | 30 | {{ wtf.quick_form(form) }} 31 | 32 | 33 | 34 |
35 |
36 |
37 | 38 | {% endblock %} -------------------------------------------------------------------------------- /api/templates/admin/add-user.html: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap/base.html' %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block styles %} 5 | {{ super() }} 6 | 10 | {% endblock %} 11 | 12 | 13 | {% block title %} 14 | Lukium Swarm - ChatGPT API 15 | {% endblock %} 16 | 17 | 18 | {% block content %} 19 | 20 |
21 |
22 |
23 | 24 |

{{ title }}

25 | {{ message_under_title }}

26 | 27 |

{{ action_instruction }}

28 | 29 | {{ wtf.quick_form(form) }} 30 | 31 |

{{ message }}
{{ message2 }}

32 | 33 |
34 |
35 |
36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /api/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap/base.html' %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block styles %} 5 | {{ super() }} 6 | 10 | {% endblock %} 11 | 12 | 13 | {% block title %} 14 | Lukium Swarm - ChatGPT API 15 | {% endblock %} 16 | 17 | 18 | {% block content %} 19 | 20 |
21 |
22 |
23 | 24 |

{{ title }}

25 | {{ message_under_title }}

26 | 27 |

{{ action_instruction }}

28 | 29 | {{ wtf.quick_form(form) }} 30 | 31 |

{{ message_after_submit_1 }}
{{ message_after_submit_2 }}

32 | 33 |
34 |
35 |
36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /api/templates/recall.html: -------------------------------------------------------------------------------- 1 | {% extends 'bootstrap/base.html' %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block styles %} 5 | {{ super() }} 6 | 10 | {% endblock %} 11 | 12 | 13 | {% block title %} 14 | Lukium Swarm - ChatGPT API 15 | {% endblock %} 16 | 17 | 18 | {% block content %} 19 | 20 |
21 |
22 |
23 | 24 |

{{ title }}

25 | {{ message_under_title }}

26 | 27 |

{{ action_instruction }}

28 | 29 | {{ wtf.quick_form(form) }} 30 | 31 |
32 |
33 |
34 | 35 | {% endblock %} -------------------------------------------------------------------------------- /classes/ChatGPT.py: -------------------------------------------------------------------------------- 1 | #IMPORT BUILT-IN LIBRARIES 2 | from datetime import datetime, timedelta 3 | import json 4 | import uuid 5 | 6 | #IMPORT THIRD-PARTY LIBRARIES 7 | import tls_client 8 | 9 | #IMPORT CONTRIBUTED-CODE LIBRARIES 10 | from contrib.OpenAIAuth.OpenAIAuth import OpenAIAuth 11 | from contrib.OpenAIAuth.Cloudflare import Cloudflare 12 | 13 | #IMPORT SETTINGS 14 | import settings.Settings as Settings 15 | 16 | #IMPORT HELPERS 17 | from helpers.General import is_valid_socks5_url, json_key_exists, add_json_key 18 | 19 | class ChatGPT: 20 | def __init__(self, **kwargs) -> None: 21 | self.instance = kwargs.get('instance') 22 | self.cf_clearance = None 23 | self.user_agent = None 24 | self.type = kwargs.get('type', 'builtin') 25 | self.user_access_token = kwargs.get('user_access_token', None) 26 | self.user_plus = kwargs.get('user_plus', 'false') 27 | self.user = kwargs.get('user', None) 28 | self.identity = "" 29 | self.session = tls_client.Session(client_identifier='chrome_108') 30 | 31 | @classmethod 32 | async def create(cls, **kwargs): 33 | instance = kwargs.get('instance') 34 | type = kwargs.get('type', 'builtin') 35 | user_access_token = kwargs.get('user_access_token', None) 36 | user_plus = kwargs.get('user_plus', 'false') 37 | user = kwargs.get('user', None) 38 | 39 | self = cls(instance=instance, type=type, user_access_token=user_access_token, user_plus=user_plus, user=user) 40 | 41 | if type == 'builtin': 42 | await self.__configure() 43 | print(f'Logging in with {self.identity}') 44 | await self.__login() 45 | elif type == 'user': 46 | await self.__configure_user() 47 | return self 48 | 49 | async def __configure_user(self): 50 | self.access_token = self.user_access_token 51 | with open('./settings.json', 'r', encoding="utf-8") as f: 52 | settings = json.load(f) 53 | api_server_settings = settings['api_server'] 54 | #Make sure the instance has a proxy in settings.json either under "opeanai" or under "api_server" 55 | if "default_proxy" in api_server_settings: 56 | proxy = api_server_settings.get("default_proxy") 57 | else: 58 | raise Exception(f'Instance {self.instance} has no proxy set, and no default proxy was found in settings.json in the "api_server" section') 59 | if type(proxy) is not str: 60 | raise Exception(f'Proxy for instance {self.instance} was found in settings.json, but it must be a string') 61 | else: 62 | if not is_valid_socks5_url(proxy): 63 | raise Exception(f'Proxy for instance {self.instance} was found in settings.json, but it is not a valid SOCKS5 proxy') 64 | else: 65 | self.proxy = proxy 66 | proxies = { 67 | "http": self.proxy, 68 | "https": self.proxy 69 | } 70 | self.session.proxies.update(proxies) 71 | #Make sure whether the instance has a plus account or not and set the attribute 72 | self.plus = self.user_plus 73 | 74 | #Use the user's APU key as identity 75 | self.identity = self.user 76 | 77 | async def __configure(self): 78 | with open('./settings.json', 'r', encoding="utf-8") as f: 79 | settings = json.load(f) 80 | instance_settings = settings['openai']['instances'][self.instance] 81 | api_server_settings = settings['api_server'] 82 | 83 | #Make sure the instance has an email 84 | if "email" not in instance_settings: 85 | raise Exception(f'No email was found for instance {self.instance} in settings.json') 86 | self.email = instance_settings.get("email") 87 | 88 | #Make sure the instance has a password 89 | if "password" not in instance_settings: 90 | raise Exception(f'No password was found for instance {self.instance} in settings.json') 91 | self.password = instance_settings.get("password") 92 | 93 | #Make sure the instance has a proxy in settings.json either under "opeanai" or under "api_server" 94 | if "proxy" in instance_settings: 95 | if instance_settings.get("proxy") == "default": 96 | if "default_proxy" in api_server_settings: 97 | proxy = api_server_settings.get("default_proxy") 98 | else: 99 | raise Exception(f'Instance {self.instance} has proxy set to default, but no default proxy was found in settings.json in the "api_server" section') 100 | else: 101 | proxy = instance_settings.get("proxy") 102 | else: 103 | if "default_proxy" in api_server_settings: 104 | proxy = api_server_settings.get("default_proxy") 105 | else: 106 | raise Exception(f'Instance {self.instance} has no proxy set, and no default proxy was found in settings.json in the "api_server" section') 107 | if type(proxy) is not str: 108 | raise Exception(f'Proxy for instance {self.instance} was found in settings.json, but it must be a string') 109 | else: 110 | if not is_valid_socks5_url(proxy): 111 | raise Exception(f'Proxy for instance {self.instance} was found in settings.json, but it is not a valid SOCKS5 proxy') 112 | else: 113 | self.proxy = proxy 114 | proxies = { 115 | "http": self.proxy, 116 | "https": self.proxy 117 | } 118 | self.session.proxies.update(proxies) 119 | 120 | #Make sure whether the instance has a plus account or not and set the attribute 121 | if "plus" in instance_settings: 122 | if type(instance_settings.get("plus")) is not bool: 123 | raise Exception(f'Attribute "plus" for instance {self.instance} was found in settings.json, but it must be a boolean') 124 | else: 125 | self.plus = instance_settings.get("plus") 126 | else: 127 | raise Exception(f'No "plus" attribute was found for instance {self.instance} in settings.json, please add it and set it to either true or false') 128 | 129 | #User email as identity for builtin accounts 130 | self.identity = self.email 131 | 132 | async def __refresh(self): 133 | self.cf_clearance = Settings.API_CF_CLEARANCE 134 | self.user_agent = Settings.API_USER_AGENT 135 | """ 136 | Refreshes the session's cookies and headers with the latest available information from login() 137 | """ 138 | self.session.cookies.clear() 139 | if Settings.API_ENDPOINT_MODE == None: 140 | self.session.cookies.update( 141 | { 142 | "cf_clearance": self.cf_clearance, 143 | #"__Secure-next-auth.session-token": self.session_token 144 | } 145 | ) 146 | else: 147 | Settings.API_USER_AGENT = Settings.API_DEFAULT_USER_AGENT 148 | self.user_agent = Settings.API_USER_AGENT 149 | 150 | self.session.headers.clear() 151 | self.session.headers.update( 152 | { 153 | "Accept": "text/event-stream", 154 | #"Accept": "application/json", 155 | "Authorization": f"Bearer {self.access_token}", 156 | "User-Agent": self.user_agent, 157 | "Content-Type": "application/json", 158 | "X-Openai-Assistant-App-Id": "", 159 | "Connection": "close", 160 | "Accept-Language": "en-US,en;q=0.9", 161 | "Referer": "https://chat.openai.com/chat", 162 | }, 163 | ) 164 | 165 | async def __login(self): 166 | if self.type == 'builtin': 167 | with open('./access_tokens.json', 'r', encoding="utf-8") as f: 168 | access_tokens = json.load(f) 169 | if self.identity in access_tokens['accounts']: 170 | if 'expires_at' in access_tokens['accounts'][self.identity]: 171 | if 'access_token' in access_tokens['accounts'][self.identity]: 172 | now = datetime.now() 173 | expires_at = datetime.strptime(access_tokens['accounts'][self.identity]['expires_at'], '%Y-%m-%d %H:%M:%S.%f') 174 | if now < expires_at: 175 | self.access_token = access_tokens['accounts'][self.identity]['access_token'] 176 | await self.__refresh() 177 | return 178 | 179 | auth = OpenAIAuth( 180 | email_address=self.email, 181 | password=self.password, 182 | proxy=self.proxy 183 | ) 184 | await auth.begin() 185 | self.access_token = await auth.get_access_token() 186 | if self.type == 'builtin': 187 | with open('./access_tokens.json', 'r', encoding="utf-8") as f: 188 | access_tokens = json.load(f) 189 | if not json_key_exists(access_tokens, 'accounts', self.identity, 'expires_at'): 190 | add_json_key(access_tokens['accounts'], {'expires_at': None}, self.identity) 191 | if not json_key_exists(access_tokens, 'accounts', self.identity, 'access_token'): 192 | add_json_key(access_tokens['accounts'], {'access_token': None}, self.identity) 193 | 194 | expires_at = datetime.now() + timedelta(seconds=Settings.OPENAI_ACCESS_TOKEN_REFRESH_INTERVAL) 195 | access_token = self.access_token 196 | access_tokens['accounts'][self.identity]['expires_at'] = str(expires_at) 197 | access_tokens['accounts'][self.identity]['access_token'] = access_token 198 | with open('./access_tokens.json', 'w', encoding="utf-8") as f: 199 | json.dump(access_tokens, f, ensure_ascii=False, indent=4) 200 | 201 | #self.session_token = await auth.get_session_token() 202 | await self.__refresh() 203 | 204 | async def refresh_cloudflare(): 205 | Settings.API_CF_CLEARANCE, Settings.API_USER_AGENT = await Cloudflare(proxy=Settings.API_DEFAULT_PROXY).a_get_cf_cookies() 206 | if len(Settings.chatgpt_instances) > 0: 207 | for instance in Settings.chatgpt_instances: 208 | instance.cf_clearance = Settings.API_CF_CLEARANCE 209 | instance.user_agent = Settings.API_USER_AGENT 210 | if len(Settings.chatgpt_free_instances) > 0: 211 | for instance in Settings.chatgpt_free_instances: 212 | instance.cf_clearance = Settings.API_CF_CLEARANCE 213 | instance.user_agent = Settings.API_USER_AGENT 214 | if len(Settings.chatgpt_plus_instances) > 0: 215 | for instance in Settings.chatgpt_plus_instances: 216 | instance.cf_clearance = Settings.API_CF_CLEARANCE 217 | instance.user_agent = Settings.API_USER_AGENT 218 | 219 | async def cloudflare(self): 220 | self.cf_clearance = Settings.API_CF_CLEARANCE 221 | self.user_agent = Settings.API_USER_AGENT 222 | 223 | async def login(self): 224 | auth = OpenAIAuth( 225 | email_address=self.email, 226 | password=self.password, 227 | proxy=self.proxy 228 | ) 229 | await auth.begin() 230 | self.access_token = await auth.get_access_token() 231 | self.session_token = await auth.get_session_token() 232 | await self.cloudflare() 233 | await self.__refresh() 234 | 235 | async def __chatgpt_post(self, **kwargs): 236 | request_body: dict = kwargs.get("request_body") 237 | endpoint: str = kwargs.get("endpoint", "conversation") 238 | conversation_id: str = kwargs.get("conversation_id", None) 239 | base_url: str = Settings.OPENAI_BASE_URL 240 | if endpoint == "conversation": 241 | url_endpoint = Settings.OPENAI_ENDPOINT_CONVERSATION 242 | elif endpoint == "get_title": 243 | url_endpoint = f'{Settings.OPENAI_ENDPOINT_GEN_TITLE}/{conversation_id}' 244 | url = f'{base_url}{url_endpoint}' 245 | response = self.session.post( 246 | url = url, 247 | headers = self.session.headers, 248 | cookies = self.session.cookies, 249 | data = json.dumps(request_body), 250 | timeout_seconds = 360 251 | ) 252 | if response.status_code == 200: 253 | return response 254 | else: 255 | error_response = {} 256 | error_response['status'] = 'error' 257 | error_response['code'] = response.status_code 258 | error_response['text'] = response.text 259 | return error_response 260 | 261 | async def __process_error(self, **kwargs): 262 | response: dict = kwargs.get("response") 263 | code = response['code'] 264 | text = response['text'] 265 | print(f'Error - Response: {text}\n\n') 266 | print(f'Error - Response Status: {code}\n\n') 267 | error_response = {} 268 | if code == 500: 269 | error_response['status'] = 'error' 270 | error_response['code'] = code 271 | error_response['message'] = text 272 | elif 'A timeout occurred' in text and code == 524: 273 | error_response['status'] = 'error' 274 | error_response['code'] = code 275 | error_response['message'] = 'A timeout occurred' 276 | elif '