diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c4fa487..742d4b3 100755 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -12,7 +12,8 @@ "vscode": { "extensions": [ "pomdtr.secrets", - "ms-python.python" + "ms-python.python", + "donjayamanne.python-extension-pack" ] } } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d4bed5b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,33 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Start Server", + "type": "python", + "request": "launch", + "program": "src/aidgaf/aidgaf-server/server.py", + "console": "integratedTerminal", + "justMyCode": true + }, + { + "name": "IDGAF", + "type": "python", + "request": "launch", + "program": "src/aidgaf/aidgaf-server/idgaf.py", + "console": "integratedTerminal", + "justMyCode": true + }, + + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/src/aidgaf/aidgaf-server/const.py b/src/aidgaf/aidgaf-server/const.py new file mode 100644 index 0000000..469e0dd --- /dev/null +++ b/src/aidgaf/aidgaf-server/const.py @@ -0,0 +1 @@ +UTF8="utf-8" \ No newline at end of file diff --git a/src/aidgaf/aidgaf-server/idgaf.py b/src/aidgaf/aidgaf-server/idgaf.py index 894bd30..a8c53dc 100644 --- a/src/aidgaf/aidgaf-server/idgaf.py +++ b/src/aidgaf/aidgaf-server/idgaf.py @@ -1,23 +1,21 @@ import json +import random +import security import re import requests -from http.server import BaseHTTPRequestHandler, HTTPServer from time import time from datetime import datetime import openai -import random import settings -import hash_calculator openai.organization = "org-hNNV1yHjZp7T3pn5pdZWaKLm" # print(openai.Model.list()) - URL = "https://api.openai.com/v1/completions" DATA = {"model": settings.OPEN_AI_COMPLETION_MODEL, "prompt": settings.PROMPTS[0], - "temperature": 0.90, + "temperature": settings.TEMPERATURE, "max_tokens": settings.OPEN_AI_MAX_TOKENS } @@ -25,35 +23,6 @@ request_headers = {"Authorization": "Bearer " + settings.APIKEY, "Content-Type": "application/json"} -class IDGAFServer(BaseHTTPRequestHandler): - - def do_PATCH(self): - # print("Request: "+self.request+ " "+webServer.get_request()) - request_body = self.rfile.read(int(self.headers.get('Content-Length'))) - str_request=str(request_body,'utf-8') - print(request_body) - if settings.HASHKEY is not None and get_message_hash(str_request) not in str_request: - print("Error: hash not match") - self.send_response(403) - return - if settings.HASHKEY is not None and not verify_message_time(str_request): - print("Error: timestamp expired") - self.send_response(403) - return - command = json.loads(request_body) - print(command) - if command['message']['command'] == 'aidgaf': - [responseCode, response_body] = parse_idgaf_request(command) - - self.handle_response(responseCode, response_body) - print("sending:"+response_body) - - def handle_response(self, code, body): - self.send_response(code) - self.send_header("Content-type", "text/html") - self.end_headers() - self.wfile.write(bytes(body, "UTF-8")) - def get_response_base_object(text): resultObject = {} @@ -64,20 +33,19 @@ def get_response_base_object(text): resultObject["timestamp"] = datetime.utcnow().timestamp() return resultObject - def parse_idgaf_request(command): the_data = get_prompt(command) - gpt_response = requests.post(URL, json=the_data, headers=request_headers) - print(gpt_response) - try: - response_text = gpt_response.json()['choices'][0]['text'].strip() + response=get_gpt_response(the_data) + try: + response_text = response.json()['choices'][0]['text'].strip() except (KeyError): - response_text=gpt_response.text - + response_text = gpt_response.text obj = get_response_base_object(response_text) - obj['hash']=get_message_hash(json.dumps(obj)) - json_result = json.dumps(obj) - return [gpt_response.status_code, json_result] + return [response.status_code, obj] + +def get_gpt_response(data): + gpt_response = requests.post(URL, json=data, headers=request_headers) + return gpt_response def get_prompt(command): @@ -93,41 +61,27 @@ def get_prompt(command): -def get_message_hash(json_command) -> bytes: - """Get the object named "message", and run a hash over it. """ - strip1 = re.sub('.*\"message\":', "", json_command, 1) - strip2 = re.sub(',\"hash\":.*', '', strip1) - json_value = bytes(strip2, "utf-8") - if (settings.HASHKEY is not None): - hash_value = hash_calculator.calculate_hash(json_value, settings.HASHKEY) - return hash_value - return "" +def get_prompt(command): + my_prompt = random.choice(settings.PROMPTS) + my_prompt = my_prompt.replace( + "USERNAME", command['message']['data']['username']) -def verify_message_time(json_command) -> bool: - """Check message expiration is less than current time + time in settings. - Before we accept the JSON as valid, and parse it, we must check the timestamp. - The timestamp is a Linux Timestamp. So convert it, add maximum message age, and - then verify current time is less than expiration time. - """ - strip_json = re.sub('.*\"timestamp\":', "", json_command, 1) - expiration = int(re.sub('}.*', '', strip_json))+settings.MAX_MESSAGE_AGE - current_time = int(time()) - return not current_time > expiration - + print("Prompt selected: "+my_prompt) + the_data = DATA + the_data["prompt"] = my_prompt + return the_data value = '{"service":"papa","message":{"command":"aidgaf","data":{"username":"AdamOutler"},"timestamp":1675725191},"hash":"1bc73914478835d03f9ebdfb46328321d2bb656647e2876d6f162cc1860607fcfca8d825c48e390a6a254ee0835c8a4fe5f9a25795a3a0880ae5a23e9c132cf2"}' if __name__ == "__main__": - if get_message_hash(value) not in value: - print("Error: hash not match") - if not verify_message_time(value): - print("Error: timestamp expired") command = json.loads(value) [code, result] = parse_idgaf_request(command) print(result) + + # curl https://api.openai.com/v1/completions \ # -H "Content-Type: application/json" \ # -H "Authorization: Bearer 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" \ diff --git a/src/aidgaf/aidgaf-server/security.py b/src/aidgaf/aidgaf-server/security.py new file mode 100644 index 0000000..3009dc3 --- /dev/null +++ b/src/aidgaf/aidgaf-server/security.py @@ -0,0 +1,45 @@ +import re +import time +import hash_calculator + +import settings +from const import UTF8 + + +def perform_hash_checks(str_request): + hash = get_message_hash(str_request) + + if hash not in str_request: + print("Error: hash not match") + return False + if not verify_message_time(str_request): + print("Error: timestamp expired") + return False + return True + + +def get_message_hash(json_command) -> str: + """Get the object named "message", and run a hash over it. """ + strip1 = re.sub(".*\"message\":", "", json_command, 1) + if ("\"hash\":" in strip1): + strip2 = re.sub(',\"hash\":.*', '', strip1) + else: + strip2 = strip1[:-1] + json_value = bytes(strip2, UTF8) + if (settings.HASHKEY is not None): + hash_value = hash_calculator.calculate_hash( + json_value, settings.HASHKEY) + return hash_value + return "" + + +def verify_message_time(json_command) -> bool: + """Check message expiration is less than current time + time in settings. + Before we accept the JSON as valid, and parse it, we must check the timestamp. + The timestamp is a Linux Timestamp. So convert it, add maximum message age, and + then verify current time is less than expiration time. + """ + strip_json = re.sub('.*\"timestamp\":', "", json_command, 1) + expiration = int(re.sub('}.*', '', strip_json))+settings.MAX_MESSAGE_AGE + current_time = int(time.time()) + return not current_time > expiration diff --git a/src/aidgaf/aidgaf-server/server.py b/src/aidgaf/aidgaf-server/server.py index 0c7a359..befe071 100644 --- a/src/aidgaf/aidgaf-server/server.py +++ b/src/aidgaf/aidgaf-server/server.py @@ -1,12 +1,77 @@ + +import json +import random + +from http.server import BaseHTTPRequestHandler, HTTPServer import idgaf -from idgaf import IDGAFServer -from http.server import HTTPServer import settings +import security +from const import UTF8 + +default_request_body = b'{"message":{"command":"aidgaf","data":{"username":"AdamOutler"}}}' + + +class IDGAFServer(BaseHTTPRequestHandler): + + def do_GET(self): + self.send_response(418) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(bytes("", UTF8)) + + def do_PATCH(self): + body = self.get_body().decode(UTF8) + if not perform_sanity_checks(body): + self.send_response(403) + return + command = json.loads(body) + self.do_request_handling(command) + + def do_request_handling(self, command): + print(command) + if command['message']['command'] == 'aidgaf': + [responseCode, json_response] = idgaf.parse_idgaf_request(command) + json_response['hash'] = security.get_message_hash(json.dumps(response_body)) + response_body = json.dumps(json_response) + self.handle_response(responseCode, response_body) + + def get_body(self): + header_length = self.headers.get('Content-Length') + request_body = default_request_body + if header_length != None and self.headers.get('Content-Length') != None: + request_body = self.rfile.read( + int(self.headers.get('Content-Length'))) + str_request = str(request_body, UTF8) + print(request_body) + return request_body + + + def handle_response(self, code, body): + self.send_response(code) + self.send_header("Content-type", "text/html") + self.end_headers() + print("sending:"+response_body) + self.wfile.write(bytes(body, UTF8)) + +def perform_sanity_checks(str_request): + if settings.HASHKEY is not None: + hash = security.get_message_hash(str_request) + if hash not in str_request: + print("Error: hash not match") + return False + if not security.verify_message_time(str_request): + print("Error: timestamp expired") + return False + return True + + def main(): - webServer = HTTPServer((settings.HOSTNAME, settings.SERVERPORT), idgaf.IDGAFServer) - print("Server started http://%s:%s" % (settings.HOSTNAME, settings.SERVERPORT)) + webServer = HTTPServer( + (settings.HOSTNAME, settings.SERVERPORT), IDGAFServer) + print("Server started http://%s:%s" % + (settings.HOSTNAME, settings.SERVERPORT)) try: webServer.serve_forever() @@ -16,5 +81,6 @@ def main(): webServer.server_close() print("Server stopped.") + if __name__ == "__main__": - main() + main() diff --git a/src/aidgaf/aidgaf-server/settings.py b/src/aidgaf/aidgaf-server/settings.py index 8a4802b..fdb4cdd 100644 --- a/src/aidgaf/aidgaf-server/settings.py +++ b/src/aidgaf/aidgaf-server/settings.py @@ -1,4 +1,5 @@ import os +from const import UTF8 # The hostname used by this app HOSTNAME: str = os.getenv('HOSTNAME') # localhost or some name # The port to broadcast the server @@ -9,14 +10,16 @@ APIKEY: str = os.getenv('APIKEY') # secret key from OpenAPI website if APIKEY is None: raise Exception("APIKEY Environmental Variable must be set") # The hash key -HASHKEY: str = None -hashKey = os.getenv('HASHKEY') # shared secret for hmac of message -if (hashKey is not None and hashKey.replace(" ", "") != ""): - HASKHEY = bytes(hashKey, "utf-8") +HASHKEY = bytes(os.getenv('HASHKEY'),UTF8) # shared secret for hmac of message + # The prompts used for OpenAI. -PROMPTS = ["Say \"USERNAME does not give a fuck\" in a thoughtful and clever paragraph of 5 sentences.", - "Say \"USERNAME does not give a fuck\" in a Dr Suess poem.", - "Tell me all about how much \"USERNAME does not give a fuck\" using your most colorful words."] +PROMPTS = [ + # "Say \"USERNAME does not give a fuck\" as a haiku and mention that it is a haiku.", + # "Say \"USERNAME does not give a fuck\" in a Dr Suess poem.", + # "Tell me a story about how \"USERNAME does not give a fuck\" using an outrageous situation where someone should care but they do not and thats fine.", + "Say \"USERNAME is completely apethetic and does not give a fuck\" in a verbose manner, using your most colorful words and one metaphor." +] OPEN_AI_MAX_TOKENS = 500 OPEN_AI_COMPLETION_MODEL = "text-davinci-003" MAX_MESSAGE_AGE = 600 +TEMPERATURE = 0.8