├── .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 '' in text:
277 | error_response['status'] = 'error'
278 | error_response['code'] = code
279 | error_response['message'] = 'Cookies Expired'
280 | elif code == 405:
281 | error_response['status'] = 'error'
282 | error_response['code'] = code
283 | error_response['message'] = 'Method Not Allowed'
284 | return error_response
285 |
286 | async def ask(self, **kwargs):
287 | await self.__refresh()
288 | user: str = kwargs.get("user")
289 | prompt: str = kwargs.get("prompt")
290 | conversation_id: str = kwargs.get("conversation_id", "")
291 | parent_message_id: str = kwargs.get("parent_message_id", "")
292 | turbo: bool = kwargs.get("turbo")
293 |
294 | message_id = str(uuid.uuid4())
295 | prompt = prompt
296 | if self.plus == True:
297 | if turbo == True:
298 | model = "text-davinci-002-render-sha"
299 | else:
300 | model = "text-davinci-002-render-sha"
301 | else:
302 | model = "text-davinci-002-render-sha"
303 |
304 | request_body = {
305 | "action": "next",
306 | "messages": [
307 | {
308 | "id": message_id,
309 | "role": "user",
310 | "content": {
311 | "content_type": "text",
312 | "parts": [
313 | prompt
314 | ]
315 | }
316 | }
317 | ],
318 | "model": model,
319 | "user": user
320 | }
321 |
322 | #Only append conversation_id if it is not empty
323 | if conversation_id != "" and conversation_id is not None:
324 | request_body["conversation_id"] = conversation_id
325 |
326 | if parent_message_id != "" and parent_message_id is not None:
327 | request_body["parent_message_id"] = parent_message_id
328 | else:
329 | request_body["parent_message_id"] = ""
330 |
331 | start_time = datetime.now() #Start counting time
332 |
333 | response = await self.__chatgpt_post(request_body=request_body)
334 |
335 | if type(response) == dict:
336 | if 'status' in response:
337 | if response['status'] == 'error':
338 | response: dict = await self.__process_error(response=response)
339 | return response
340 |
341 | step_1 = str(response.text).replace('data: [DONE]','').strip() # Remove the "data: [DONE]" from the response as well as leading and trailing whitespace
342 | step_2 = step_1.rfind('data: {"message": {"id": "') # Find the last occurance of the string (data: {"message": {"id": ") in the response
343 | step_3 = (step_1[step_2:]).strip() # Slice the response from the last occurance of the string (data: {"message": {"id": ") to the end of the response and remove leading and trailing whitespace
344 | step_4 = step_3.find(' ') # Find the first occurance of a space in the response
345 | step_5 = (step_3[step_4:]).strip() # Slice the response from the beginning of the response to the first occurance of a space and remove leading and trailing whitespace
346 | #print(f'Step 5: {step_5}')
347 | response = json.loads(step_5) # Convert the response to a dictionary
348 |
349 | #GENERATE RESPONSE TITLE
350 | if conversation_id == "" or conversation_id is None: #Only generate title if conversation_id is not provided, which means it's a new conversation
351 | response['conversation_title'] = await self.__gen_title(conversation_id=response['conversation_id'], message_id=response['message']['id'])
352 |
353 | #ATTACH USER'S USERNAME TO RESPONSE
354 | response['api_user'] = Settings.API_KEYS[user]['username']
355 |
356 | end_time = datetime.now() #Start counting time
357 | response_time = str(round((end_time - start_time).total_seconds(), 2)) #Calculate time difference
358 |
359 | response['api_response_time_taken'] = response_time #Add response time to response object
360 | response['api_prompt_time_origin_readable'] = start_time.strftime('%Y-%m-%d | %H:%M:%S')
361 | response['api_prompt_time_origin'] = str(start_time)
362 | response['api_prompt'] = prompt #Add prompt to response object
363 | response['api_instance_type'] = self.type #Add instance type to response object
364 | response['api_instance_identity'] = self.identity #Add instance identity to response object
365 |
366 | if 'status' not in response:
367 | response['status'] = 'success'
368 |
369 | return response
370 |
371 | async def __gen_title(self, **kwargs):
372 | conversation_id = kwargs.get('conversation_id')
373 | message_id = kwargs.get('message_id')
374 | request_body = {
375 | "message_id": message_id,
376 | "model": "text-davinci-002-render"
377 | }
378 | response = json.loads((await self.__chatgpt_post(request_body=request_body, endpoint="get_title", conversation_id=conversation_id)).text)
379 | if 'title' in response:
380 | return response['title']
381 | else:
382 | return 'Error Generating Title'
--------------------------------------------------------------------------------
/classes/ChatGPTClient.py:
--------------------------------------------------------------------------------
1 | from helpers.General import add_json_key
2 |
3 | class ChatGPTClient:
4 | def __init__ (self, **kwargs) -> None:
5 | identity = kwargs.get('identity', None)
6 | self.users: dict = {}
7 | self.user_ids: list = []
8 | self.identity: str = identity
9 |
10 | @classmethod
11 | async def create(cls, **kwargs) -> 'ChatGPTClient':
12 | server_users: dict = kwargs.get('server_users', {})
13 | client_users: list = kwargs.get('client_users', [])
14 | client_id: str = kwargs.get('client_id', None)
15 | self = cls(identity = client_id)
16 | await self.__load_client_users(client_users = client_users, server_users = server_users)
17 | return self
18 |
19 | async def __load_client_users(self, **kwargs) -> None:
20 |
21 | client_users = kwargs.get('client_users', [])
22 | server_users = kwargs.get('server_users', {})
23 |
24 | if len(client_users) == 0:
25 | return
26 | else:
27 | self.users = {}
28 | for user in client_users:
29 | self.users[user] = server_users[user]
30 | self.user_ids.append(server_users[user]['user_id'])
--------------------------------------------------------------------------------
/classes/ChatGPTServer.py:
--------------------------------------------------------------------------------
1 | #IMPORT BUILT-IN LIBRARIES
2 | from copy import deepcopy
3 | from datetime import datetime
4 | import json
5 |
6 | #IMPORT THIRD-PARTY LIBRARIES
7 | import markdown
8 | from markdown.extensions.codehilite import CodeHiliteExtension
9 |
10 | #IMPORT CONTRIBUTED-CODE LIBRARIES
11 | from contrib.OpenAIAuth.OpenAIAuth import OpenAIAuth
12 | from contrib.OpenAIAuth.Cloudflare import Cloudflare
13 |
14 | #IMPORT CLASSES
15 | from classes.ChatGPT import ChatGPT
16 | from classes.ChatGPTClient import ChatGPTClient
17 |
18 | #IMPORT HELPER FUNCTIONS
19 | from helpers.General import generate_api_key, json_value_exists, add_json_key
20 |
21 | import settings.Settings as Settings
22 |
23 | from htmls.Blocks import *
24 |
25 | class ChatGPTServer:
26 | def __init__(self, **kwargs) -> None:
27 | self.current_chatgpt_any = 0
28 | self.current_chatgpt_free = 0
29 | self.current_chatgpt_plus = 0
30 | self.users: dict = {}
31 | self.conversations: dict = {}
32 | self.chatgpt_instances: list = []
33 | self.chatgpt_free_instances: list = []
34 | self.chatgpt_plus_instances: list = []
35 | self.clients: list = []
36 | with open('./settings.json', 'r', encoding="utf-8") as f:
37 | self.settings = json.load(f)
38 |
39 | @classmethod
40 | async def create(cls, **kwargs) -> 'ChatGPTServer':
41 | """
42 | Creates a new instance of the ChatGPTServer class asynchronously and returns it
43 | """
44 | current_chatgpt_any = 0
45 | current_chatgpt_free = 0
46 | current_chatgpt_plus = 0
47 | with open('./settings.json', 'r', encoding="utf-8") as f:
48 | settings = json.load(f)
49 | self = cls(current_chatgpt_any=current_chatgpt_any, current_chatgpt_free=current_chatgpt_free, current_chatgpt_plus=current_chatgpt_plus, settings=settings)
50 | await self.__load_chatgpt_instances()
51 | await self.__refresh_cloudflare()
52 | await self.__load_api_users()
53 | await self.__load_client_users()
54 | await self.__load_conversations()
55 | return self
56 |
57 | #SETUP CHATGPT INSTANCES
58 | async def __load_chatgpt_instances(self) -> None:
59 | """
60 | Loads all ChatGPT instances from settings.json
61 | """
62 | openai_instances = self.settings['openai']['instances']
63 | self.chatgpt_instances = [await ChatGPT.create(instance=openai_instances.index(instance)) for instance in openai_instances]
64 | if len(self.chatgpt_instances) == 0:
65 | raise Exception('No OpenAI instances were found in settings.json')
66 | self.chatgpt_free_instances = [instance for instance in self.chatgpt_instances if instance.plus == False]
67 | self.chatgpt_plus_instances = [instance for instance in self.chatgpt_instances if instance.plus == True]
68 | print(f'ChatGPT Free Instances Loaded: {len(self.chatgpt_free_instances)}')
69 | print(f'ChatGPT Plus Instances Loaded: {len(self.chatgpt_plus_instances)}')
70 | Settings.API_ENDPOINT_MODE = 'bp'
71 |
72 | async def __load_api_users(self) -> dict:
73 | """
74 | Loads all API users from users.json
75 | """
76 | with open('./users.json', 'r', encoding="utf-8") as f:
77 | users = (json.load(f))['API_KEYS']
78 | self.users = users
79 | Settings.API_KEYS = users
80 |
81 | async def __reload_client_users(self, **kwargs) -> None:
82 | """
83 | Reloads users belonging to a client
84 | """
85 | client_id_to_reload = kwargs.get('client_id_to_reload', None)
86 |
87 | client_instance: ChatGPTClient = [server_client for server_client in self.clients if server_client.identity == client_id_to_reload][0]
88 |
89 | for user, user_data in self.users.items():
90 | for client in user_data['clients']:
91 | if client == client_id_to_reload:
92 | if user not in client_instance.users:
93 | client_instance.users[user] = user_data
94 | client_instance.user_ids.append(user_data['user_id'])
95 |
96 | async def __load_client_users(self):
97 | """
98 | Loads all client users from users.json
99 | """
100 | clients_to_add = set()
101 |
102 | client_users_dict = {}
103 | for user, user_data in self.users.items():
104 | for client in user_data['clients']:
105 | if client != 'self':
106 | if client not in client_users_dict:
107 | client_users_dict[client] = []
108 | client_users_dict[client].append(user)
109 |
110 | clients_to_add = [(client, users) for client, users in client_users_dict.items() if len(users) > 0]
111 |
112 | for user, user_data in self.users.items():
113 | add_client = True
114 | if 'is_client' in user_data:
115 | if user_data['is_client'] == True:
116 | for client in clients_to_add:
117 | if client[0] == user_data['user_id']:
118 | add_client = False
119 | break
120 | if add_client == True:
121 | clients_to_add.append((user_data['user_id'], []))
122 |
123 | for client, client_users in clients_to_add:
124 | self.clients.append(await ChatGPTClient.create(client_id=client, client_users=client_users, server_users=self.users))
125 | print(f'Client {client} loaded with {len(client_users)} {len(client_users) == 1 and "user" or "users"}')
126 |
127 | async def __load_conversations(self) -> dict:
128 | """
129 | Loads all conversations from conversations.json
130 | """
131 | with open('./conversations.json', 'r', encoding="utf-8") as f:
132 | conversations = (json.load(f))
133 | self.conversations = conversations
134 |
135 | async def __refresh_cloudflare(self) -> None:
136 | """
137 | Refreshes the Cloudflare Clearance and User Agent
138 | """
139 | if Settings.API_ENDPOINT_MODE == None:
140 | current_time = datetime.now()
141 | if Settings.API_LAST_CF_REFRESH is not None:
142 | time_delta = (current_time - Settings.API_LAST_CF_REFRESH).total_seconds()
143 | if time_delta > Settings.API_CF_REFRESH_INTERVAL:
144 | refresh = True
145 | else:
146 | refresh = False
147 | else:
148 | refresh = True
149 | if refresh:
150 | print(f'Starting Cloudflare Refresh')
151 | print(f'Current Cloudflare Clearance: {Settings.API_CF_CLEARANCE}')
152 | print(f'Current User Agent: {Settings.API_USER_AGENT}')
153 | Settings.API_CF_CLEARANCE, Settings.API_USER_AGENT = await Cloudflare(proxy=Settings.API_DEFAULT_PROXY).a_get_cf_cookies()
154 | Settings.API_LAST_CF_REFRESH = datetime.now()
155 | print(f'New Cloudflare Clearance: {Settings.API_CF_CLEARANCE}')
156 | print(f'New User Agent: {Settings.API_USER_AGENT}')
157 | if len(self.chatgpt_instances) > 0:
158 | for instance in self.chatgpt_instances:
159 | instance.cf_clearance = Settings.API_CF_CLEARANCE
160 | instance.user_agent = Settings.API_USER_AGENT
161 | if len(self.chatgpt_free_instances) > 0:
162 | for instance in self.chatgpt_free_instances:
163 | instance.cf_clearance = Settings.API_CF_CLEARANCE
164 | instance.user_agent = Settings.API_USER_AGENT
165 | if len(self.chatgpt_plus_instances) > 0:
166 | for instance in self.chatgpt_plus_instances:
167 | instance.cf_clearance = Settings.API_CF_CLEARANCE
168 | instance.user_agent = Settings.API_USER_AGENT
169 | else:
170 | print(f'Cloudflare Refresh Not Needed - Last Refresh: {Settings.API_LAST_CF_REFRESH} - Time Delta: {time_delta} - Interval: {Settings.API_CF_REFRESH_INTERVAL}')
171 | else:
172 | Settings.OPENAI_BASE_URL = Settings.OPENAI_BASE_URL.replace('chat', 'apps').replace('backend-', '')
173 |
174 | #SETUP HELPER FUNCTIONS
175 | async def __get_chatgpt(self, **kwargs) -> ChatGPT:
176 | """
177 | Returns a ChatGPT instance based on the type of instance requested.
178 | """
179 | type: str = kwargs.get('type', None)
180 | if type == 'any':
181 | chatgpt = self.chatgpt_instances[self.current_chatgpt_any]
182 | self.current_chatgpt_any += 1
183 | if self.current_chatgpt_any >= len(self.chatgpt_instances):
184 | self.current_chatgpt_any = 0
185 | elif type == 'free':
186 | chatgpt = self.chatgpt_free_instances[self.current_chatgpt_free]
187 | self.current_chatgpt_free += 1
188 | if self.current_chatgpt_free >= len(self.chatgpt_free_instances):
189 | self.current_chatgpt_free = 0
190 | elif type == 'plus':
191 | chatgpt = self.chatgpt_plus_instances[self.current_chatgpt_plus]
192 | self.current_chatgpt_plus += 1
193 | if self.current_chatgpt_plus >= len(self.chatgpt_plus_instances):
194 | self.current_chatgpt_plus = 0
195 | return chatgpt
196 |
197 | async def check_user(self, **kwargs) -> dict:
198 | """
199 | Check if a user is valid
200 | """
201 | user = kwargs.get('user', None)
202 | response: dict = {}
203 |
204 | if user is None:
205 | response['status'] = 'error'
206 | response['message'] = 'No user specified'
207 | return response
208 | elif user not in self.users:
209 | response['status'] = 'error'
210 | response['message'] = 'Invalid user specified'
211 | return response
212 | else:
213 | response['status'] = 'success'
214 | response['message'] = 'User is valid'
215 |
216 | return response
217 |
218 | async def check_admin(self, **kwargs) -> dict:
219 | """
220 | Check if admin is valid
221 | """
222 | await self.__load_api_users()
223 | user = kwargs.get('user', None)
224 | response: dict = {}
225 |
226 | if user is None:
227 | response['status'] = 'error'
228 | response['message'] = 'No user specified'
229 | return response
230 | elif user not in self.users:
231 | response['status'] = 'error'
232 | response['message'] = 'Invalid user specified'
233 | return response
234 | else:
235 | if 'is_admin' in self.users[user]:
236 | if self.users[user]['is_admin'] == True:
237 | response['status'] = 'success'
238 | response['message'] = 'Admin validated'
239 | else:
240 | response['status'] = 'error'
241 | response['message'] = 'Admin not validated'
242 | else:
243 | response['status'] = 'error'
244 | response['message'] = 'Admin not validated'
245 |
246 | return response
247 |
248 | async def check_client(self, **kwargs) -> dict:
249 | """
250 | Check if a client is valid
251 | """
252 | await self.__load_api_users()
253 | user = kwargs.get('user', None)
254 | response: dict = {}
255 |
256 | if user is None:
257 | response['status'] = 'error'
258 | response['message'] = 'No client specified'
259 | return response
260 | elif user not in self.users:
261 | response['status'] = 'error'
262 | response['message'] = 'Invalid client specified'
263 | return response
264 | else:
265 | if 'is_client' in self.users[user]:
266 | if self.users[user]['is_client'] == True:
267 | response['status'] = 'success'
268 | response['message'] = 'Client validated'
269 | else:
270 | response['status'] = 'error'
271 | response['message'] = 'Client not validated'
272 | else:
273 | response['status'] = 'error'
274 | response['message'] = 'Admin not validated'
275 |
276 | return response
277 |
278 | async def client_user_id_check(self, **kwargs) -> dict:
279 | """
280 | Check if a client user exists, if so return its API Key, otherwise create a new user and return its API Key
281 | """
282 | await self.__load_api_users()
283 | client = kwargs.get('client')
284 | user_id = kwargs.get('user_id')
285 | username = kwargs.get('username')
286 | user_id_plus = kwargs.get('user_id_plus')
287 |
288 | response: dict = {}
289 |
290 | client_id: str = self.users[client]['user_id']
291 |
292 | client_instance: ChatGPTClient = [server_client for server_client in self.clients if server_client.identity == client_id][0]
293 |
294 | if user_id not in client_instance.user_ids:
295 | status, message, key = await self.add_user(client = client, userid = user_id, username = username, plus = user_id_plus)
296 | response['status'] = status
297 | response['message'] = message
298 | response['key'] = key
299 | await self.__reload_client_users(client_id_to_reload=client_id)
300 | else:
301 | for key, value in client_instance.users.items():
302 | if value['user_id'] == user_id:
303 | response['status'] = 'success'
304 | response['message'] = 'User already exists'
305 | response['key'] = key
306 | break
307 |
308 | return response
309 |
310 | async def retrieve_access_token(self, **kwargs) -> str:
311 | """
312 | Retrieve an access token for a user
313 | """
314 | email = kwargs.get('email', None)
315 | password = kwargs.get('password', None)
316 | auth = OpenAIAuth(
317 | email_address=email,
318 | password=password,
319 | proxy=Settings.API_DEFAULT_PROXY
320 | )
321 | await auth.begin()
322 | access_token = await auth.get_access_token()
323 |
324 | return access_token
325 |
326 | async def __spawn_user_chatgpt(self, **kwargs) -> ChatGPT:
327 | """
328 | Spawn a new ChatGPT instance for a user
329 | """
330 | user: str = kwargs.get('user', None)
331 | access_token: str = kwargs.get('access_token', None)
332 | user_plus: str = kwargs.get('user_plus', 'false')
333 |
334 | chatgpt = await ChatGPT.create(instance=0,
335 | cf_clearance=Settings.API_CF_CLEARANCE,
336 | user_agent=Settings.API_USER_AGENT,
337 | type='user',
338 | user_access_token=access_token,
339 | user_plus=user_plus,
340 | user=user
341 | )
342 | return chatgpt
343 |
344 | async def __store_conversation(self, **kwargs) -> None:
345 | """
346 | Store conversation data in JSON file
347 | """
348 | client_id: str = kwargs.get("client_id")
349 | user: str = kwargs.get("user")
350 | response: dict = kwargs.get("response")
351 | user_id: str = Settings.API_KEYS[user]['user_id']
352 | conversation_id: str = response['conversation_id']
353 | message_id: str = response['message']['id']
354 |
355 | #Build conversation data
356 | data = {
357 | 'prompt': response['api_prompt'],
358 | 'reply': response['message']['content']['parts'][0],
359 | 'origin_time': response['api_prompt_time_origin'],
360 | }
361 |
362 | conversations_users = deepcopy(self.conversations)
363 | api_users = self.users
364 |
365 | if user_id not in conversations_users['users']:
366 | add_json_key(conversations_users['users'], {'username': api_users[user]['username'], 'conversations': {}}, user_id)
367 | user_conversations = conversations_users['users'][user_id]['conversations']
368 |
369 | if conversation_id not in user_conversations:
370 | add_json_key(user_conversations, {'title': response['conversation_title'], 'api_instance_type': response['api_instance_type'], 'api_instance': response['api_instance_identity'], 'messages': {}}, conversation_id)
371 | conversation_messages = user_conversations[conversation_id]['messages']
372 |
373 | if message_id not in conversation_messages:
374 | add_json_key(conversation_messages, data, message_id)
375 |
376 | with open('conversations.json', 'w', encoding="utf-8") as f:
377 | json.dump(conversations_users, f, ensure_ascii=False, indent=4)
378 |
379 | await self.__load_conversations()
380 |
381 | async def __sort_conversation_chronologically(self, **kwargs) -> list:
382 | """
383 | Sort a dictionary by value of subkey
384 | """
385 | user_messages = kwargs.get('user_messages', None)
386 | for k, v in user_messages.items():
387 | v["origin_time"] = datetime.strptime(v["origin_time"], "%Y-%m-%d %H:%M:%S.%f")
388 | sorted_list = sorted(user_messages.items(), key=lambda x: x[1]["origin_time"])
389 | return sorted_list
390 |
391 | async def recall(self, **kwargs) -> dict:
392 | """
393 | Recalls a conversation from the conversation database
394 | """
395 | user: str = kwargs.get('user', None)
396 | conversation_id: str = kwargs.get('conversation_id', "")
397 | user_id = self.users[user]['user_id']
398 |
399 | #Create a copy of the conversation data using deepcopy to avoid modifying the original data
400 | user_conversations = deepcopy(self.conversations['users'][user_id]['conversations'])
401 |
402 | response = {}
403 | if conversation_id == "":
404 | for key in user_conversations:
405 | response[key] = {
406 | 'title': user_conversations[key]['title'],
407 | }
408 | else:
409 | response['conversation_id'] = conversation_id
410 | response['conversation_title'] = user_conversations[conversation_id]['title']
411 |
412 | user_messages = user_conversations[conversation_id]['messages']
413 | sorted_messages = await self.__sort_conversation_chronologically(user_messages=user_messages)
414 | for message in sorted_messages:
415 | message[1].pop('origin_time')
416 | final_list: list = []
417 | for message in sorted_messages:
418 | message_dict = {
419 | message[0] : message[1]
420 | }
421 | final_list.append(message_dict)
422 | response['messages'] = final_list
423 |
424 | return response
425 |
426 | async def process_chagpt_request(self, **kwargs) -> dict:
427 | """
428 | Process a ChatGPT request
429 | """
430 | if Settings.API_ENDPOINT_MODE == None:
431 | await self.__refresh_cloudflare()
432 | client: str = kwargs.get('client', 'self')
433 | user: str = kwargs.get('user', None)
434 | prompt: str = kwargs.get('prompt', Settings.API_DEFAULT_PROMPT)
435 | conversation_id: str = kwargs.get('conversation_id', '')
436 | plus: str = kwargs.get('plus', 'any')
437 | type: str = kwargs.get('type', 'builtin')
438 | access_token: str = kwargs.get('access_token', None)
439 | user_plus: str = kwargs.get('user_plus', 'false')
440 |
441 | chatgpt: ChatGPT = None #Initialize ChatGPT instance
442 |
443 | response: dict = {}
444 | followup: bool = False
445 | conversation_last_message_id: str = None
446 |
447 | if client == 'self' or client is None:
448 | client_id = 'self'
449 | else:
450 | client_id = self.users[client]['user_id']
451 |
452 | user_id = self.users[user]['user_id']
453 |
454 | if conversation_id != "" and conversation_id is not None:
455 | followup = True
456 | conversation_users = self.conversations['users']
457 |
458 | #Check if user has conversations
459 | if user_id not in conversation_users:
460 | response['status'] = 'error'
461 | response['message'] = 'This user does not appear to have any conversations. Please start a new conversation using this user.'
462 | return response
463 | else:
464 | user_conversations = conversation_users[user_id]['conversations']
465 |
466 | #Check if conversation exists
467 | if conversation_id not in user_conversations:
468 | response['status'] = 'error'
469 | response['message'] = 'Conversation not found. It either does not exist or has been purged from the cache. Please try again with a new conversation id or start a new conversation.'
470 | return response
471 | else:
472 | conversation = user_conversations[conversation_id]
473 | conversation_title = conversation['title']
474 | api_instance_type = conversation['api_instance_type']
475 |
476 | #Check if conversation was started using the same API Instance type
477 | if type != api_instance_type:
478 | response['status'] = 'error'
479 | if type == 'builtin':
480 | response['message'] = 'Conversation found, but was started using an Access Token. Please use the Access Token to continue the conversation.'
481 | elif type == 'user':
482 | response['message'] = 'Conversation found, but was started using a Builtin API Instance. Please remove the Access Token to continue the conversation using Builtin API Instance.'
483 | return response
484 |
485 | user_messages = deepcopy(conversation['messages'])
486 | sorted_messages = await self.__sort_conversation_chronologically(user_messages=user_messages)
487 | conversation_last_message_id = sorted_messages[-1][0]
488 | last_message_prompt = sorted_messages[-1][1]['prompt']
489 | last_message_reply = sorted_messages[-1][1]['reply']
490 |
491 | if type == 'builtin':
492 | target_instance = conversation['api_instance']
493 | chatgpt: ChatGPT = next((x for x in self.chatgpt_instances if x.identity == target_instance), None)
494 | if chatgpt is None:
495 | response['status'] = 'error'
496 | response['message'] = 'Conversation found, but the previously used API Instance was not found. Please try again.'
497 | return response
498 | elif type == 'user':
499 | chatgpt: ChatGPT = await self.__spawn_user_chatgpt(user=user, access_token=access_token, user_plus=user_plus)
500 |
501 | else:
502 | if type == 'user':
503 | chatgpt: ChatGPT = await self.__spawn_user_chatgpt(user=user, access_token=access_token, user_plus=user_plus)
504 |
505 | elif type == 'builtin':
506 | if plus == "any":
507 | chatgpt: ChatGPT = await self.__get_chatgpt(type='any')
508 | elif plus == "false":
509 | chatgpt: ChatGPT = await self.__get_chatgpt(type='free')
510 | elif plus == "true":
511 | chatgpt: ChatGPT = await self.__get_chatgpt(type='plus')
512 |
513 | response: dict = await chatgpt.ask(user=user, prompt=prompt, conversation_id=conversation_id, parent_message_id=conversation_last_message_id)
514 |
515 | if followup:
516 | response['conversation_title'] = conversation_title
517 | response['last_message_prompt'] = last_message_prompt
518 | response['last_message_reply'] = last_message_reply
519 |
520 | response['api_client_id'] = client_id
521 |
522 | if response['status'] == 'success':
523 | await self.__store_conversation(client_id=client_id, user=user, response=response) #Store conversation in database
524 | del response['api_instance_type']
525 | del response['api_instance_identity']
526 |
527 | return response
528 |
529 | async def process_reply_only(self, **kwargs) -> str:
530 | """
531 | Processes a reply only response from the API and returns a formatted string
532 | """
533 | response: dict = kwargs.get('response', None)
534 | pretty: str = kwargs.get('pretty', 'false')
535 | if 'status' in response:
536 | if response['status'] == 'error':
537 | return response['message']
538 | if 'message' in response:
539 | if 'content' in response['message']:
540 | if 'parts' in response['message']['content']:
541 | if len(response['message']['content']['parts']) >= 0:
542 | well_formed_response = True
543 | reply_only_response = response['message']['content']['parts'][0]
544 | if pretty == "true":
545 | md = markdown.Markdown(extensions=['fenced_code', 'nl2br', CodeHiliteExtension(linenums=True, guess_lang=True, use_pygmnet=True, css_class='highlight')])
546 | reply_only_response = md.convert(reply_only_response)
547 | reply_only_response = HTML_HEADER + reply_only_response + HTML_FOOTER
548 | return reply_only_response
549 | else:
550 | return reply_only_response
551 |
552 | if well_formed_response:
553 | return "Error: Malformed response"
554 |
555 | async def get_user_from_user_id(self, **kwargs) -> dict:
556 | """
557 | Returns the user API key from the user id
558 | """
559 | user_id: str = kwargs.get('user_id', None)
560 |
561 | response = {'status': 'success', 'message': 'User found', 'user': None}
562 |
563 | for user in self.users:
564 | if self.users[user]['user_id'] == user_id:
565 | response['user'] = user
566 | return response
567 |
568 | response['status'] = 'error'
569 | response['message'] = 'User not found'
570 | return response
571 |
572 |
573 | async def remove_conversation(self, **kwargs) -> dict:
574 | """
575 | Removes a conversation from the database
576 | """
577 | user: str = kwargs.get('user', None)
578 | conversation_id: str = kwargs.get('conversation_id', None)
579 | response: dict = {'status': 'success', 'message': 'Conversation removed successfully'}
580 |
581 | user_id = self.users[user]['user_id']
582 | if conversation_id in self.conversations['users'][user_id]['conversations']:
583 | del self.conversations['users'][user_id]['conversations'][conversation_id]
584 | await self.__save_conversations()
585 | return response
586 | else:
587 | response['status'] = 'error'
588 | response['message'] = 'Conversation not found'
589 | return response
590 |
591 | async def __save_conversations(self) -> None:
592 | """
593 | Saves the conversations to the database
594 | """
595 |
596 | with open('./conversations.json', 'w') as f:
597 | json.dump(self.conversations, f, indent=4)
598 |
599 | async def add_user(self, **kwargs) -> tuple:
600 | """
601 | Adds a user to the user database
602 | """
603 | await self.__load_api_users()
604 | client: str = kwargs.get('client', 'self')
605 | userid: str = kwargs.get('userid', None)
606 | username: str = kwargs.get('username', None)
607 | plus: bool = kwargs.get('plus', False)
608 | is_client: bool = kwargs.get('is_client', False)
609 |
610 | if client != 'self':
611 | client_id = self.users[client]['user_id']
612 | else:
613 | client_id = client
614 |
615 | if json_value_exists(Settings.API_KEYS, userid):
616 | status = 'error'
617 | message = 'User already exists'
618 | key = None
619 | return status, message, key
620 | else:
621 | api_key = generate_api_key()
622 | add_json_key(Settings.API_KEYS, {
623 | 'user_id': userid,
624 | 'username': username,
625 | 'plus': plus,
626 | 'clients': [client_id],
627 | 'is_client': is_client,
628 | }, api_key)
629 | with open('./users.json', 'r', encoding="utf-8") as f:
630 | keys = json.load(f)
631 | keys['API_KEYS'] = Settings.API_KEYS
632 | with open('./users.json', 'w', encoding="utf-8") as f:
633 | json.dump(keys, f, ensure_ascii=False, indent=4)
634 | await self.__load_api_users()
635 | await Settings.reload_users()
636 | if json_value_exists(Settings.API_KEYS, userid):
637 | status = 'success'
638 | message = 'User added'
639 | key = api_key
640 | await self.__load_api_users()
641 | return status, message, key
642 | else:
643 | status = 'error'
644 | message = 'User not added'
645 | key = None
646 | return status, message, key
--------------------------------------------------------------------------------
/contrib/OpenAIAuth/Cloudflare.py:
--------------------------------------------------------------------------------
1 | # Credits to github.com/rawandahmad698/PyChatGPT and https://github.com/acheong08/OpenAIAuth
2 |
3 | """
4 | Gets cf_clearance details
5 | """
6 | import re
7 | from time import sleep
8 |
9 | import undetected_chromedriver as uc
10 |
11 |
12 | class Cloudflare:
13 | """
14 | Gets cloudflare clearance via browser automation.
15 | """
16 |
17 | def __init__(
18 | self,
19 | proxy: str = None,
20 | driver_exec_path: str = None,
21 | browser_exec_path: str = None,
22 | ) -> None:
23 | self.proxy: str = proxy
24 | self.cf_clearance: str = None
25 | self.user_agent: str = None
26 | self.driver_exec_path: str = driver_exec_path
27 | self.browser_exec_path: str = browser_exec_path
28 | self.cf_cookie_found: bool = False
29 | self.agent_found: bool = False
30 |
31 | def __get_chrome_options(self):
32 | options = uc.ChromeOptions()
33 | options.add_argument("--start_maximized")
34 | options.add_argument("--disable-extensions")
35 | options.add_argument("--disable-application-cache")
36 | options.add_argument("--disable-gpu")
37 | options.add_argument("--no-sandbox")
38 | options.add_argument("--disable-setuid-sandbox")
39 | options.add_argument("--disable-dev-shm-usage")
40 | if self.proxy:
41 | options.add_argument("--proxy-server=" + self.proxy)
42 | return options
43 |
44 | def __detect_cookies(self, message):
45 | if "params" in message:
46 | if "headers" in message["params"]:
47 | if "set-cookie" in message["params"]["headers"]:
48 | # Use regex to get the cookie for cf_clearance=*;
49 | cf_clearance_cookie = re.search(
50 | "cf_clearance=.*?;",
51 | message["params"]["headers"]["set-cookie"],
52 | )
53 | if cf_clearance_cookie and not self.cf_cookie_found:
54 | print("Found Cloudflare Cookie!")
55 | # remove the semicolon and 'cf_clearance=' from the string
56 | raw_cf_cookie = cf_clearance_cookie.group(0)
57 | self.cf_clearance = raw_cf_cookie.split("=")[1][:-1]
58 | self.cf_cookie_found = True
59 |
60 | def __detect_user_agent(self, message):
61 | if "params" in message:
62 | if "headers" in message["params"]:
63 | if "user-agent" in message["params"]["headers"]:
64 | # Use regex to get the cookie for cf_clearance=*;
65 | user_agent = message["params"]["headers"]["user-agent"]
66 | self.user_agent = user_agent
67 | self.agent_found = True
68 |
69 | def get_cf_cookies(self) -> tuple:
70 | """
71 | Get cloudflare cookies.
72 |
73 | :return: None
74 | """
75 | driver = None
76 | try:
77 | self.cf_cookie_found = False
78 | self.agent_found = False
79 | self.cf_clearance = None
80 | self.user_agent = None
81 | options = self.__get_chrome_options()
82 | print("Spawning browser...")
83 | driver = uc.Chrome(
84 | enable_cdp_events=True,
85 | options=options,
86 | driver_executable_path=self.driver_exec_path,
87 | browser_executable_path=self.browser_exec_path,
88 | )
89 | print("Browser spawned.")
90 | driver.add_cdp_listener(
91 | "Network.responseReceivedExtraInfo",
92 | lambda msg: self.__detect_cookies(msg),
93 | )
94 | driver.add_cdp_listener(
95 | "Network.requestWillBeSentExtraInfo",
96 | lambda msg: self.__detect_user_agent(msg),
97 | )
98 | driver.get("https://chat.openai.com/chat")
99 | while not self.agent_found or not self.cf_cookie_found:
100 | sleep(5)
101 | finally:
102 | # Close the browser
103 | if driver is not None:
104 | driver.quit()
105 | del driver
106 | return self.cf_clearance, self.user_agent
107 |
108 | async def a_get_cf_cookies(self) -> tuple:
109 | """
110 | Get cloudflare cookies.
111 |
112 | :return: None
113 | """
114 | driver = None
115 | try:
116 | self.cf_cookie_found = False
117 | self.agent_found = False
118 | self.cf_clearance = None
119 | self.user_agent = None
120 | options = self.__get_chrome_options()
121 | print("Spawning browser...")
122 | driver = uc.Chrome(
123 | enable_cdp_events=True,
124 | options=options,
125 | driver_executable_path=self.driver_exec_path,
126 | browser_executable_path=self.browser_exec_path,
127 | )
128 | print("Browser spawned.")
129 | driver.add_cdp_listener(
130 | "Network.responseReceivedExtraInfo",
131 | lambda msg: self.__detect_cookies(msg),
132 | )
133 | driver.add_cdp_listener(
134 | "Network.requestWillBeSentExtraInfo",
135 | lambda msg: self.__detect_user_agent(msg),
136 | )
137 | driver.get("https://chat.openai.com/chat")
138 | while not self.agent_found or not self.cf_cookie_found:
139 | sleep(5)
140 | finally:
141 | # Close the browser
142 | if driver is not None:
143 | driver.quit()
144 | del driver
145 | return self.cf_clearance, self.user_agent
--------------------------------------------------------------------------------
/contrib/OpenAIAuth/OpenAIAuth.py:
--------------------------------------------------------------------------------
1 | # Credits to github.com/rawandahmad698/PyChatGPT and https://github.com/acheong08/OpenAIAuth
2 |
3 | #IMPORT BUILT-IN MODULES
4 | import base64
5 | import re
6 | import urllib
7 |
8 | #IMPORT THIRD-PARTY MODULES
9 | import tls_client
10 |
11 |
12 | class Debugger:
13 | def __init__(self, debug: bool = False):
14 | if debug:
15 | print("Debugger enabled on OpenAIAuth")
16 | self.debug = debug
17 |
18 | def set_debug(self, debug: bool):
19 | self.debug = debug
20 |
21 | def log(self, message: str, end: str = "\n"):
22 | if self.debug:
23 | print(message, end=end)
24 |
25 |
26 | class OpenAIAuth:
27 | def __init__(
28 | self,
29 | email_address: str,
30 | password: str,
31 | proxy: str = None,
32 | debug: bool = False,
33 | ):
34 | self.session_token = None
35 | self.email_address = email_address
36 | self.password = password
37 | self.proxy = proxy
38 | self.session = tls_client.Session(
39 | client_identifier="chrome_109",
40 | )
41 | self.access_token: str = None
42 | self.debugger = Debugger(debug)
43 | self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"
44 |
45 | @staticmethod
46 | def url_encode(string: str) -> str:
47 | """
48 | URL encode a string
49 | :param string:
50 | :return:
51 | """
52 | return urllib.parse.quote(string)
53 |
54 | async def begin(self) -> None:
55 | """
56 | Begin the auth process
57 | """
58 | self.debugger.log("Beginning auth process")
59 | if not self.email_address or not self.password:
60 | return
61 |
62 | if self.proxy:
63 | proxies = {
64 | "http": self.proxy,
65 | "https": self.proxy,
66 | }
67 | self.session.proxies = proxies
68 |
69 | # First, make a request to https://explorer.api.openai.com/auth/login
70 | url = "https://explorer.api.openai.com/"
71 | headers = {
72 | "Host": "ask.openai.com",
73 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
74 | "User-Agent": self.user_agent,
75 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
76 | "Accept-Encoding": "gzip, deflate, br",
77 | "Connection": "keep-alive",
78 | }
79 |
80 | response = self.session.get(
81 | url=url,
82 | headers=headers,
83 | )
84 | if response.status_code == 200:
85 | self.__part_two()
86 | else:
87 | self.debugger.log("Error in part one")
88 | self.debugger.log("Response: ", end="")
89 | self.debugger.log(response.text)
90 | self.debugger.log("Status code: ", end="")
91 | self.debugger.log(response.status_code)
92 | raise Exception("API error")
93 |
94 | def __part_two(self) -> None:
95 | """
96 | In part two, We make a request to https://explorer.api.openai.com/api/auth/csrf and grab a fresh csrf token
97 | """
98 | self.debugger.log("Beginning part two")
99 |
100 | url = "https://explorer.api.openai.com/api/auth/csrf"
101 | headers = {
102 | "Host": "ask.openai.com",
103 | "Accept": "*/*",
104 | "Connection": "keep-alive",
105 | "User-Agent": self.user_agent,
106 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
107 | "Referer": "https://explorer.api.openai.com/auth/login",
108 | "Accept-Encoding": "gzip, deflate, br",
109 | }
110 | response = self.session.get(
111 | url=url,
112 | headers=headers,
113 | )
114 | if response.status_code == 200 and "json" in response.headers["Content-Type"]:
115 | csrf_token = response.json()["csrfToken"]
116 | self.__part_three(token=csrf_token)
117 | else:
118 | self.debugger.log("Error in part two")
119 | self.debugger.log("Response: ", end="")
120 | self.debugger.log(response.text)
121 | self.debugger.log("Status code: ", end="")
122 | self.debugger.log(response.status_code)
123 | raise Exception("Error logging in")
124 |
125 | def __part_three(self, token: str) -> None:
126 | """
127 | We reuse the token from part to make a request to /api/auth/signin/auth0?prompt=login
128 | """
129 | self.debugger.log("Beginning part three")
130 | url = "https://explorer.api.openai.com/api/auth/signin/auth0?prompt=login"
131 | payload = f"callbackUrl=%2F&csrfToken={token}&json=true"
132 | headers = {
133 | "Host": "explorer.api.openai.com",
134 | "User-Agent": self.user_agent,
135 | "Content-Type": "application/x-www-form-urlencoded",
136 | "Accept": "*/*",
137 | "Sec-Gpc": "1",
138 | "Accept-Language": "en-US,en;q=0.8",
139 | "Origin": "https://explorer.api.openai.com",
140 | "Sec-Fetch-Site": "same-origin",
141 | "Sec-Fetch-Mode": "cors",
142 | "Sec-Fetch-Dest": "empty",
143 | "Referer": "https://explorer.api.openai.com/auth/login",
144 | "Accept-Encoding": "gzip, deflate",
145 | }
146 | self.debugger.log("Payload: " + payload)
147 | self.debugger.log("Payload length: " + str(len(payload)))
148 | response = self.session.post(url=url, headers=headers, data=payload)
149 | if response.status_code == 200 and "json" in response.headers["Content-Type"]:
150 | url = response.json()["url"]
151 | if (
152 | url
153 | == "https://explorer.api.openai.com/api/auth/error?error=OAuthSignin"
154 | or "error" in url
155 | ):
156 | self.debugger.log("You have been rate limited")
157 | raise Exception("You have been rate limited.")
158 | self.__part_four(url=url)
159 | else:
160 | self.debugger.log("Error in part three")
161 | self.debugger.log("Response: ", end="")
162 | self.debugger.log("Status code: ", end="")
163 | self.debugger.log(response.status_code)
164 | self.debugger.log(response.headers)
165 | self.debugger.log(self.session.cookies.get_dict())
166 | raise Exception("Unknown error")
167 |
168 | def __part_four(self, url: str) -> None:
169 | """
170 | We make a GET request to url
171 | :param url:
172 | :return:
173 | """
174 | self.debugger.log("Beginning part four")
175 | headers = {
176 | "Host": "auth0.openai.com",
177 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
178 | "Connection": "keep-alive",
179 | "User-Agent": self.user_agent,
180 | "Accept-Language": "en-US,en;q=0.9",
181 | "Referer": "https://explorer.api.openai.com/",
182 | }
183 | response = self.session.get(
184 | url=url,
185 | headers=headers,
186 | )
187 | if response.status_code == 302:
188 | try:
189 | state = re.findall(r"state=(.*)", response.text)[0]
190 | state = state.split('"')[0]
191 | self.__part_five(state=state)
192 | except IndexError as exc:
193 | self.debugger.log("Error in part four")
194 | self.debugger.log("Status code: ", end="")
195 | self.debugger.log(response.status_code)
196 | self.debugger.log("Rate limit hit")
197 | self.debugger.log("Response: " + str(response.text))
198 | raise Exception("Rate limit hit") from exc
199 | else:
200 | self.debugger.log("Error in part four")
201 | self.debugger.log("Response: ", end="")
202 | self.debugger.log(response.text)
203 | self.debugger.log("Status code: ", end="")
204 | self.debugger.log(response.status_code)
205 | self.debugger.log("Wrong response code")
206 | raise Exception("Unknown error")
207 |
208 | def __part_five(self, state: str) -> None:
209 | """
210 | We use the state to get the login page & check for a captcha
211 | """
212 | self.debugger.log("Beginning part five")
213 | url = f"https://auth0.openai.com/u/login/identifier?state={state}"
214 |
215 | headers = {
216 | "Host": "auth0.openai.com",
217 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
218 | "Connection": "keep-alive",
219 | "User-Agent": self.user_agent,
220 | "Accept-Language": "en-US,en;q=0.9",
221 | "Referer": "https://explorer.api.openai.com/",
222 | }
223 | response = self.session.get(url, headers=headers)
224 | if response.status_code == 200:
225 | self.__part_six(state=state)
226 | else:
227 | self.debugger.log("Error in part five")
228 | self.debugger.log("Response: ", end="")
229 | self.debugger.log(response.text)
230 | self.debugger.log("Status code: ", end="")
231 | self.debugger.log(response.status_code)
232 | raise ValueError("Invalid response code")
233 |
234 | def __part_six(self, state: str) -> None:
235 | """
236 | We make a POST request to the login page with the captcha, email
237 | :param state:
238 | :return:
239 | """
240 | self.debugger.log("Beginning part six")
241 | url = f"https://auth0.openai.com/u/login/identifier?state={state}"
242 | email_url_encoded = self.url_encode(self.email_address)
243 |
244 | payload = (
245 | f"state={state}&username={email_url_encoded}&js-available=false&webauthn-available=true&is"
246 | f"-brave=false&webauthn-platform-available=true&action=default "
247 | )
248 |
249 | headers = {
250 | "Host": "auth0.openai.com",
251 | "Origin": "https://auth0.openai.com",
252 | "Connection": "keep-alive",
253 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
254 | "User-Agent": self.user_agent,
255 | "Referer": f"https://auth0.openai.com/u/login/identifier?state={state}",
256 | "Accept-Language": "en-US,en;q=0.9",
257 | "Content-Type": "application/x-www-form-urlencoded",
258 | }
259 | response = self.session.post(
260 | url,
261 | headers=headers,
262 | data=payload,
263 | )
264 | if response.status_code == 302:
265 | self.__part_seven(state=state)
266 | else:
267 | self.debugger.log("Error in part six")
268 | self.debugger.log("Response: ", end="")
269 | self.debugger.log(response.text)
270 | self.debugger.log("Status code: ", end="")
271 | self.debugger.log(response.status_code)
272 | raise Exception("Unknown error")
273 |
274 | def __part_seven(self, state: str) -> None:
275 | """
276 | We enter the password
277 | :param state:
278 | :return:
279 | """
280 | url = f"https://auth0.openai.com/u/login/password?state={state}"
281 | self.debugger.log("Beginning part seven")
282 | email_url_encoded = self.url_encode(self.email_address)
283 | password_url_encoded = self.url_encode(self.password)
284 | payload = f"state={state}&username={email_url_encoded}&password={password_url_encoded}&action=default"
285 | headers = {
286 | "Host": "auth0.openai.com",
287 | "Origin": "https://auth0.openai.com",
288 | "Connection": "keep-alive",
289 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
290 | "User-Agent": self.user_agent,
291 | "Referer": f"https://auth0.openai.com/u/login/password?state={state}",
292 | "Accept-Language": "en-US,en;q=0.9",
293 | "Content-Type": "application/x-www-form-urlencoded",
294 | }
295 | try:
296 | response = self.session.post(
297 | url,
298 | headers=headers,
299 | data=payload,
300 | )
301 | self.debugger.log("Request went through")
302 | except Exception as exc:
303 | self.debugger.log("Error in part seven")
304 | self.debugger.log("Exception: ", end="")
305 | self.debugger.log(exc)
306 | raise Exception("Could not get response") from exc
307 | if response.status_code == 302:
308 | self.debugger.log("Response code is 302")
309 | try:
310 | new_state = re.findall(r"state=(.*)", response.text)[0]
311 | new_state = new_state.split('"')[0]
312 | self.debugger.log("New state found")
313 | self.__part_eight(old_state=state, new_state=new_state)
314 | except Exception as exc:
315 | raise Exception("Could not find new state") from exc
316 | else:
317 | self.debugger.log("Error in part seven")
318 | self.debugger.log("Status code: ", end="")
319 | self.debugger.log(response.status_code)
320 | wrong_combo_substring = 'Wrong email or password'
321 | if wrong_combo_substring in response.text:
322 | self.access_token = 'Wrong email or password provided'
323 | else:
324 | raise Exception("Wrong status code")
325 |
326 | def __part_eight(self, old_state: str, new_state) -> None:
327 | self.debugger.log("Beginning part eight")
328 | url = f"https://auth0.openai.com/authorize/resume?state={new_state}"
329 | headers = {
330 | "Host": "auth0.openai.com",
331 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
332 | "Connection": "keep-alive",
333 | "User-Agent": self.user_agent,
334 | "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8",
335 | "Referer": f"https://auth0.openai.com/u/login/password?state={old_state}",
336 | }
337 | response = self.session.get(
338 | url,
339 | headers=headers,
340 | allow_redirects=True,
341 | )
342 | if response.status_code == 200:
343 | self.session_token = response.cookies.get_dict()[
344 | "__Secure-next-auth.session-token"
345 | ]
346 |
347 | async def get_session_token(self):
348 | #print(f'Session Token: {self.session_token}')
349 | return self.session_token
350 |
351 | async def get_access_token(self):
352 | """
353 | Gets access token
354 | """
355 | if self.access_token == 'Wrong email or password provided':
356 | return self.access_token
357 | response = self.session.get(
358 | "https://explorer.api.openai.com/api/auth/session",
359 | )
360 | if response.status_code == 200:
361 | self.access_token = response.json()["accessToken"]
362 | self.debugger.log("Access token found")
363 | return self.access_token
364 | else:
365 | self.debugger.log("Error in part nine")
366 | self.debugger.log("Status code: ", end="")
367 | self.debugger.log(response.status_code)
368 | raise Exception("Wrong status code")
--------------------------------------------------------------------------------
/conversations.json:
--------------------------------------------------------------------------------
1 | {
2 | "users": {
3 |
4 | }
5 | }
--------------------------------------------------------------------------------
/forms/FormAccessToken.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import FlaskForm
2 | from wtforms import StringField, SubmitField, PasswordField
3 | from wtforms.validators import DataRequired
4 |
5 | from settings.Settings import LANG
6 |
7 | LANG = LANG['forms']['access_token']
8 |
9 | class FormAccessToken(FlaskForm):
10 | api_key = StringField(label=f'{LANG["labels"]["api_key"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["api_key"]}')
11 | email = StringField(label=f'{LANG["labels"]["email"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["email"]}')
12 | password = PasswordField(label=f'{LANG["labels"]["password"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["password"]}')
13 | submit = SubmitField(label=f'{LANG["labels"]["submit"]}')
--------------------------------------------------------------------------------
/forms/FormAddUser.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import FlaskForm
2 | from wtforms import StringField, SubmitField, SelectField
3 | from wtforms.validators import DataRequired
4 |
5 | from settings.Settings import LANG
6 |
7 | LANG = LANG['forms']['add_user']
8 |
9 | class FormAddUser(FlaskForm):
10 | plus = SelectField(label=f'{LANG["labels"]["plus"]}', validators=[DataRequired()], choices=[('true', f'{LANG["choices"]["yes"]}'), ('false', f'{LANG["choices"]["no"]}')], default='false', description=f'{LANG["descriptions"]["plus"]}')
11 | is_client = SelectField(label=f'{LANG["labels"]["is_client"]}', validators=[DataRequired()], choices=[('true', f'{LANG["choices"]["yes"]}'), ('false', f'{LANG["choices"]["no"]}')], default='false', description=f'{LANG["descriptions"]["is_client"]}')
12 | userid = StringField(label=f'{LANG["labels"]["userid"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["userid"]}')
13 | username = StringField(label=f'{LANG["labels"]["username"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["username"]}')
14 | submit = SubmitField(label=f'{LANG["labels"]["submit"]}')
--------------------------------------------------------------------------------
/forms/FormChatGPT.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import FlaskForm
2 | from wtforms import StringField, SubmitField, TextAreaField, SelectField
3 | from wtforms.validators import DataRequired
4 |
5 | from settings.Settings import LANG
6 |
7 | LANG = LANG['forms']['chatgpt']
8 |
9 | class FormChatGPT(FlaskForm):
10 | api_key = StringField(label=f'{LANG["labels"]["api_key"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["api_key"]}')
11 | prompt = TextAreaField(label=f'{LANG["labels"]["prompt"]}', validators=[DataRequired()])
12 | conversation_id = StringField(label=f'{LANG["labels"]["conversation_id"]}', description=f'{LANG["descriptions"]["conversation_id"]}')
13 | plus = SelectField(label=f'{LANG["labels"]["plus"]}', choices=[('any', f'{LANG["choices"]["first_available"]}'), ('true', f'{LANG["choices"]["plus_only"]}'), ('false', f'{LANG["choices"]["free_only"]}')], default='false', description=f'{LANG["descriptions"]["plus"]}')
14 | reply_only = SelectField(label=f'{LANG["labels"]["reply_only"]}', choices=[('false', f'{LANG["choices"]["no"]}'), ('true', f'{LANG["choices"]["yes"]}')], default='true', description=f'{LANG["descriptions"]["reply_only"]}')
15 | pretty = SelectField(label=f'{LANG["labels"]["pretty"]}', choices=[('false', f'{LANG["choices"]["no"]}'), ('true', f'{LANG["choices"]["yes"]}')], default='true', description=f'{LANG["descriptions"]["pretty"]}')
16 | access_token = StringField(label=f'{LANG["labels"]["access_token"]}', description=f'{LANG["descriptions"]["access_token"]}')
17 | user_plus = SelectField(label=f'{LANG["labels"]["user_plus"]}', choices=[('true', f'{LANG["choices"]["yes"]}'), ('false', f'{LANG["choices"]["no"]}')], default='false', description=f'{LANG["descriptions"]["user_plus"]}')
18 | submit = SubmitField(label=f'{LANG["labels"]["submit"]}')
--------------------------------------------------------------------------------
/forms/FormChatRecall.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import FlaskForm
2 | from wtforms import StringField, SubmitField
3 | from wtforms.validators import DataRequired
4 |
5 | from settings.Settings import LANG
6 |
7 | LANG = LANG['forms']['chatrecall']
8 |
9 | class FormChatRecall(FlaskForm):
10 | api_key = StringField(label=f'{LANG["labels"]["api_key"]}', validators=[DataRequired()], description=f'{LANG["descriptions"]["api_key"]}')
11 | conversation_id = StringField(label=f'{LANG["labels"]["conversation_id"]}', description=f'{LANG["descriptions"]["conversation_id"]}')
12 | submit = SubmitField(label=f'{LANG["labels"]["submit"]}')
--------------------------------------------------------------------------------
/helpers/General.py:
--------------------------------------------------------------------------------
1 | #IMPORT BUILT-IN LIBRARIES
2 | import re
3 | import secrets
4 | import string
5 |
6 | def is_valid_socks5_url(url: str) -> bool:
7 | """
8 | Validates if a string is a valid Socks5 URL.
9 |
10 | Args:
11 | url (str): The URL to validate.
12 |
13 | Returns:
14 | bool: True if the URL is a valid Socks5 URL, False otherwise.
15 | """
16 |
17 | # Socks5 URL regex pattern
18 | pattern = r'^socks5://(\d{1,3}\.){3}\d{1,3}:\d{1,5}$'
19 |
20 | # Check if the URL matches the pattern
21 | if re.match(pattern, url):
22 | return True
23 |
24 | return False
25 |
26 | def generate_api_key(length=32) -> str:
27 | """
28 | Generates a random API key
29 | """
30 | chars = string.ascii_letters + string.digits
31 | while True:
32 | api_key = ''.join(secrets.choice(chars) for _ in range(length))
33 | if (any (c.islower() for c in api_key)
34 | and any (c.isupper() for c in api_key)
35 | and sum (c.isdigit() for c in api_key) >= 3):
36 | break
37 | return api_key
38 |
39 | def json_key_exists(data, *keys):
40 | """
41 | Checks if a key exists in a JSON dictionary
42 | """
43 | if isinstance(data, dict):
44 | if keys[0] in data.keys():
45 | if len(keys) > 1:
46 | return json_key_exists(data[keys[0]], *keys[1:])
47 | else:
48 | return True
49 | else:
50 | return False
51 | else:
52 | return False
53 |
54 | def json_value_exists(data, value):
55 | if isinstance(data, dict):
56 | for val in data.values():
57 | if json_value_exists(val, value):
58 | return True
59 | elif data == value:
60 | return True
61 | return False
62 |
63 | def add_json_key(data, value, *keys):
64 | """
65 | Adds a key to a JSON dictionary
66 | """
67 | if len(keys) == 1:
68 | data[keys[0]] = value
69 | else:
70 | if keys[0] not in data.keys():
71 | data[keys[0]] = {}
72 | add_json_key(data[keys[0]], value, *keys[1:])
--------------------------------------------------------------------------------
/htmls/Blocks.py:
--------------------------------------------------------------------------------
1 | HTML_HEADER = """
2 |
3 |
4 |
5 |
6 |
7 |
ChatGPT
8 |
79 |
80 |
81 | """
82 | HTML_FOOTER = """
83 |
84 |
85 | """
86 |
--------------------------------------------------------------------------------
/localizations/en-us.json:
--------------------------------------------------------------------------------
1 | {
2 | "htmls":{
3 | "index": {
4 | "title": "Lukium Swarm - ChatGPT API",
5 | "message_under_title": "For Experiment and Learning Only",
6 | "action_instruction": "Please enter your prompt below as well as your Lukium Swarm - ChatGPT API Key",
7 | "message_after_submit_1": "Please be patient while your request is processed...",
8 | "message_after_submit_2": "The time required to process is proportional to the length of the reply."
9 | },
10 | "recall": {
11 | "title": "Lukium Swarm - ChatGPT API",
12 | "message_under_title": "For Experiment and Learning Only",
13 | "action_instruction": "Please enter your API Key and Conversation ID below"
14 | },
15 | "access_token": {
16 | "title": "Lukium Swarm - ChatGPT API",
17 | "message_under_title": "For Experiment and Learning Only",
18 | "action_instruction": "Please enter your ChatGPT Email and Password below to retrieve your access token",
19 | "message": "This will retrieve your current ChatGPT OpenAI Access Token using your ChatGPT Email and Password."
20 | },
21 | "add_user": {
22 | "title": "Lukium Swarm - ChatGPT API",
23 | "message_under_title": "For Experiment and Learning Only",
24 | "action_instruction": "Please enter information below to add a new API key for a user"
25 | }
26 | },
27 | "forms":{
28 | "access_token": {
29 | "labels": {
30 | "api_key": "API Key",
31 | "email": "ChatGPT Email",
32 | "password": "ChatGPT Password",
33 | "submit": "Submit"
34 | },
35 | "descriptions": {
36 | "api_key": "OVERRIDEN IF PASSED VIA URL [user=] . Lukium Swarm API Key, NOT OpeanAI API Key",
37 | "email": "Your OpenAI ChatGPT Email. This is the email that you use to login to the OpenAI ChatGPT website.",
38 | "password": "Your OpenAI ChatGPT Password. This is the password that you use to login to the OpenAI ChatGPT website.",
39 | "submit": ""
40 | }
41 | },
42 | "add_user": {
43 | "labels": {
44 | "plus": "Plus Access?",
45 | "is_client": "Client Access?",
46 | "userid": "User ID",
47 | "username": "Username",
48 | "submit": "Submit"
49 | },
50 | "descriptions": {
51 | "plus": "Should the user have access to the Plus API?",
52 | "is_client": "A Client is a user that can create and manage their own API keys and store conversations within their own key in conversations.json. This is useful for developers who want to create their own applications that use the API.",
53 | "userid": "The user's ID. This is the ID that will be used to identify the user in the API. This can be any string, but it is recommended to use a unique ID such as a Discord ID or a UUID.",
54 | "username": "The user's username. This is the name that will be displayed in the API. This can be any string, but it is recommended to use the user's Discord username or some other name attached to another platform.",
55 | "submit": ""
56 | },
57 | "choices": {
58 | "yes": "Yes",
59 | "no": "No"
60 | }
61 | },
62 | "chatgpt": {
63 | "labels": {
64 | "api_key": "Lukium Swarm API Key",
65 | "prompt": "Prompt",
66 | "conversation_id": "Conversation ID",
67 | "plus": "Use Plus or Free?",
68 | "reply_only": "Reply Only?",
69 | "pretty": "Markdown Reply?",
70 | "access_token": "OpenAI Access Token",
71 | "user_plus": "OpenAI Plus?",
72 | "submit": "Submit"
73 | },
74 | "descriptions": {
75 | "api_key": "OVERRIDEN IF PASSED VIA URL [user=] . Lukium Swarm API Key, NOT OpeanAI API Key",
76 | "prompt": "",
77 | "conversation_id": "OPTIONAL - Used to continue a conversation. If not provided, a new conversation will be started.",
78 | "plus": "Use Plus or Free (Plus only available for Premium+ Supporters)",
79 | "reply_only": "Get only the reply, not the full JSON response'",
80 | "pretty": "Ignored if \"Reply Only\" = \"No\" | Use markdown styling (prettier) for the response?'",
81 | "access_token": "OPTIONAL - OVERRIDEN IF PASSED VIA URL [access_token=] . OpenAI Access Token, NOT Lukium Swarm API Key. Can be retrieved using /access-token endpoint",
82 | "user_plus": "Does this access token have OpenAI Plus?",
83 | "submit": ""
84 | },
85 | "choices": {
86 | "yes": "Yes",
87 | "no": "No",
88 | "first_available": "First Available",
89 | "plus_only" : "Plus Only",
90 | "free_only" : "Free Only"
91 | }
92 | },
93 | "chatrecall": {
94 | "labels": {
95 | "api_key": "API Key",
96 | "conversation_id": "Conversation ID",
97 | "submit": "Submit"
98 | },
99 | "descriptions": {
100 | "api_key": "OVERRIDEN IF PASSED VIA URL [user=] . Lukium Swarm API Key, NOT OpeanAI API Key",
101 | "conversation_id": "If NOT passed, will retrieve a list of all conversations' IDs and their titles. If PASSED, will retrieve the conversation's title and all messages' IDs, prompts and replies.",
102 | "submit": ""
103 | }
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | import logging
2 | logging.getLogger("urllib3").setLevel(logging.ERROR)
3 |
4 | #IMPORT SETTINGS
5 | import settings.Settings as Settings
6 |
7 | #IMPORT SERVER APP
8 | from api.ChatGPT_Server import app
9 |
10 | if __name__ == '__main__':
11 | app.run(host=Settings.API_HOST, port=Settings.API_PORT, threaded=True)
12 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | tls_client
2 | flask[async]
3 | Flask-WTF
4 | Flask-Bootstrap4
5 | markdown
6 | pygments
7 | undetected_chromedriver
--------------------------------------------------------------------------------
/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "openai": {
3 | "instances": [
4 | {
5 | "email": "email1@email.com",
6 | "password": "password1",
7 | "plus": true
8 | },
9 | {
10 | "email": "email2@email.com",
11 | "password": "password2",
12 | "plus": false
13 | }
14 | ],
15 | "access_token_refresh_interval": 86400,
16 | "base_url": "https://chat.openai.com/backend-api",
17 | "endpoints":{
18 | "conversation": "/conversation",
19 | "gen_title": "/conversation/gen_title"
20 | }
21 | },
22 | "api_server": {
23 | "webui_language": "en-US",
24 | "host": "192.168.11.11",
25 | "port": 5000,
26 | "default_proxy": "socks5://127.0.0.1:1080",
27 | "default_user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36",
28 | "terminal_name": "ChatGPT-API-Server",
29 | "cf_refresh_interval": 600,
30 | "endpoints": {
31 | "browser": {
32 | "chatgpt": {
33 | "url": "/chat"
34 | },
35 | "chatrecall": {
36 | "url": "/recall"
37 | },
38 | "access_token": {
39 | "url": "/access-token"
40 | },
41 | "add_user": {
42 | "url": "/admin/add-user"
43 | }
44 | },
45 | "api": {
46 | "chatgpt": {
47 | "url": "/api/chat"
48 | },
49 | "chatrecall": {
50 | "url": "/api/recall"
51 | },
52 | "access_token": {
53 | "url": "/api/access-token"
54 | },
55 | "add_user": {
56 | "url": "/api/client/add-user"
57 | }
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/settings/Settings.py:
--------------------------------------------------------------------------------
1 | #IMPORT BUILT-IN LIBRARIES
2 | from datetime import date, datetime
3 | import json
4 | import os
5 | import platform
6 |
7 | #LOAD SETTINGS FROM SETTINGS.JSON
8 | with open('./settings.json', 'r', encoding="utf-8") as f:
9 | settings = json.load(f)
10 |
11 | #SETUP API TERMINAL NAME
12 | API_TERMINAL_NAME = os.environ.get("API_TERMINAL_NAME") or str(settings['api_server']['terminal_name'])
13 |
14 | #NAME TERMINAL BASED ON OS BEFORE ANYTHING ELSE
15 | if platform.system() == 'Windows':
16 | os.system(f'title {API_TERMINAL_NAME}')
17 | else:
18 | os.system(f'echo -e "\033]0;{API_TERMINAL_NAME}\007"')
19 |
20 | #CONTINUE IMPORTS
21 |
22 | #IMPORT HELPER FUNCTIONS
23 | from helpers.General import generate_api_key, json_key_exists, add_json_key
24 |
25 | #SETUP APP SECRET IF NOT PRESENT
26 | update_settings = False
27 | if not json_key_exists(settings, 'api_server', 'app_secret'): #if admin_api_key is not present in settings.json
28 | add_json_key(settings, generate_api_key(), 'api_server', 'app_secret') #add admin_api_key to settings.json
29 | update_settings = True
30 | if update_settings:
31 | with open('./settings.json', 'w', encoding="utf-8") as f:
32 | json.dump(settings, f, ensure_ascii=False, indent=4)
33 | with open('./settings.json', 'r',encoding="utf-8") as f:
34 | settings = json.load(f)
35 |
36 | #SETUP ADMIN API KEY IF NOT PRESENT
37 | #Load API keys from users.json
38 | with open('./users.json', 'r', encoding="utf-8") as f:
39 | users = json.load(f)
40 | API_KEYS = users['API_KEYS']
41 |
42 | update_users = False
43 |
44 | if len(API_KEYS) == 0:
45 | update_users = True
46 | new_key = generate_api_key()
47 | add_json_key(API_KEYS, {'user_id': 'changeme', 'username': 'changeme', 'plus': False, 'is_admin': True, 'is_client': True, 'clients': ['self']}, new_key)
48 |
49 | if update_users:
50 | users['API_KEYS'] = API_KEYS
51 | with open('./users.json', 'w', encoding="utf-8") as f:
52 | json.dump(users, f, indent=4)
53 | with open('./users.json', 'r', encoding="utf-8") as f:
54 | users = json.load(f)
55 | API_KEYS = users['API_KEYS']
56 |
57 | async def reload_users():
58 | """
59 | Reloads the API user database
60 | """
61 | global API_KEYS
62 | with open('./users.json', 'r', encoding="utf-8") as f:
63 | users = json.load(f)
64 | API_KEYS = users['API_KEYS']
65 |
66 | #SETUP OPENAI API DEFAULTS
67 | OPENAI_ACCESS_TOKEN_REFRESH_INTERVAL = int(settings['openai']['access_token_refresh_interval'])
68 | OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL") or str(settings['openai']['base_url'])
69 | OPENAI_ENDPOINT_CONVERSATION = os.environ.get("OPENAI_CONVERSATION_ENDPOINT") or str(settings['openai']['endpoints']['conversation'])
70 | OPENAI_ENDPOINT_GEN_TITLE = os.environ.get("OPENAI_GEN_TITLE_ENDPOINT") or str(settings['openai']['endpoints']['gen_title'])
71 | OPENAI_BASE_PROMPT = (
72 | f'You are ChatGPT, a large language model trained by OpenAI. Respond conversationally. Do not answer as the user. Current date: {str(date.today())}.\n\n'
73 | f'User: Hello\n'
74 | f'ChatGPT: Hello! How can I help you today? <|im_end|>\n\n\n'
75 | )
76 |
77 | #INITIALIZE CLOUDFLARE VARIABLES
78 | API_CF_CLEARANCE: str = None
79 | API_USER_AGENT: str = None
80 | API_LAST_CF_REFRESH: datetime = None
81 |
82 | #SETUP API DEFAULTS
83 | API_LANGUAGE="en-US"
84 | API_DEFAULT_PROXY = settings['api_server']['default_proxy']
85 | API_DEFAULT_USER_AGENT = settings['api_server']['default_user_agent']
86 | API_APP_SECRET = settings['api_server']['app_secret']
87 | API_CF_REFRESH_INTERVAL = settings['api_server']['cf_refresh_interval']
88 | API_HOST = os.environ.get("API_HOST") or str(settings['api_server']['host'])
89 | API_PORT = os.environ.get("API_PORT") or int(settings['api_server']['port'])
90 | API_LOCAL_BASE_URL = f'http://{API_HOST}:{API_PORT}'
91 | API_DEFAULT_PROMPT = (
92 | f'Say the following: You did not enter a prompt. URL should be http://
:/chat?prompt=\n'
93 | f'Join us at https://discord.gg/lukium for the best source of AI resources and tools,'
94 | f'including free Stable Diffusion Servers running on dedicated RTX 3090 GPUs,'
95 | f'as well as a community of AI enthusiasts.'
96 | )
97 |
98 | #SETUP API ENDPOINT URL CONSTANS
99 | API_ENDPOINT_MODE = None
100 | BROWSER_ENDPOINTS = settings['api_server']['endpoints']['browser']
101 | API_ENDPOINTS = settings['api_server']['endpoints']['api']
102 |
103 | ENDPOINT_BROWSER_CHATGPT = BROWSER_ENDPOINTS['chatgpt']['url']
104 | ENDPOINT_BROWSER_CHATRECALL = BROWSER_ENDPOINTS['chatrecall']['url']
105 | ENDPOINT_BROWSER_ACCESS_TOKEN = BROWSER_ENDPOINTS['access_token']['url']
106 | ENDPOINT_BROWSER_ADD_USER = BROWSER_ENDPOINTS['add_user']['url']
107 |
108 | ENDPOINT_API_CHATGPT = API_ENDPOINTS['chatgpt']['url']
109 | ENDPOINT_API_CHATRECALL = API_ENDPOINTS['chatrecall']['url']
110 | ENDPOINT_API_ACCESS_TOKEN = API_ENDPOINTS['access_token']['url']
111 | ENDPOINT_API_ADD_USER = API_ENDPOINTS['add_user']['url']
112 | ENDPOINT_API_REMOVE_CONVERSATION = API_ENDPOINTS['remove_conversation']['url']
113 |
114 | #SETUP WEBUI LANGUAGE
115 | LANG: dict = {}
116 | API_WEBUI_LANGUAGE = settings['api_server']['webui_language']
117 | try:
118 | API_WEBUI_LANGUAGE_FILE = f'./localizations/{API_WEBUI_LANGUAGE}.json'
119 | except:
120 | raise Exception(f'Invalid webui language: {API_WEBUI_LANGUAGE}')
121 | with open(API_WEBUI_LANGUAGE_FILE, 'r') as f:
122 | LANG = json.load(f)
123 |
--------------------------------------------------------------------------------
/shinysocks/README.md:
--------------------------------------------------------------------------------
1 | # ShinySOCKS
2 |
3 | ## Mission Statement
4 |
5 | *To create a small, ultrafast SOCKS proxy server.
6 |
7 | ## Background
8 |
9 | I sometimes use VPN. In one case, I have a VPN access only
10 | from a Windows 7 virtual machine trough some proprietary
11 | "security by obscurity" VPN software. In order to work
12 | efficiently, I need to connect my Linux workstation to that
13 | VPN. Network routing and IP forwarding seems not to work,
14 | so the second best option in my case is SOCKS. Socks
15 | trough Putty works, kind of. It's slow and unreliable.
16 |
17 | I tried a few free SOCKS servers. Neither of them worked, so
18 | therefore I'm spending a few hours writing my own.
19 |
20 | ## Current State
21 | The project is currently under initial development.
22 |
23 | The SOCKS server works for SOCKS 4, 4a and 5 under
24 | Linux and Windows (compiled under Windows 7 with Visual
25 | Studio 2015 RC and Boost 1.58). IPv6 and binding (reverse
26 | connections) are not yet supported.
27 |
28 | ## How I use it
29 |
30 | I start ShinySOCKS on the command-line (cmd.exe) ina Windows 7
31 | VM with VPN. Then I ssh to whatever servers on the VPN network
32 | I desire - using ShinySOCKS as a proxy.
33 |
34 | From Linux:
35 | $ ssh -o ProxyCommand='nc -x 192.168.0.10:1080 %h %p' jgaa@cool-server
36 |
37 |
38 | ## License
39 | ShinySOCKS is released under GPLv3.
40 | It is Free.
41 | Free as in Free Beer.
42 | Free as in Free Air.
43 |
--------------------------------------------------------------------------------
/shinysocks/shinysocks.conf:
--------------------------------------------------------------------------------
1 | ; Example configuration file.
2 |
3 | interfaces {
4 | interface {
5 | ; Use to listen on all interfaces
6 | ; hostname "0.0.0.0"
7 |
8 | ; Use for testing on localhost
9 | hostname "127.0.0.1"
10 | port 1080
11 | }
12 | }
13 |
14 |
15 | system {
16 | ; Number of io-threads
17 | io-threads 4
18 | }
19 |
20 | log {
21 | file "shinysocks_%5N.log"
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/shinysocks/shinysocks.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lukium/chatgpt-api-server/c7914716214f28a0fff566fc069ca0a02195efae/shinysocks/shinysocks.exe
--------------------------------------------------------------------------------
/users.json:
--------------------------------------------------------------------------------
1 | {
2 | "API_KEYS": {
3 |
4 | }
5 | }
--------------------------------------------------------------------------------