diff --git a/garfmain.py b/garfmain.py index bf0e754..4b900a0 100644 --- a/garfmain.py +++ b/garfmain.py @@ -5,9 +5,8 @@ import subprocess from garfpy import( logger, is_private, - kroger_token, find_store, search_product, - garfpic, process_image_requests, generate_chat, - aod_message, wikisum, generate_qr, GarfbotRespond) + aod_message, generate_qr, + Kroger, GarfAI, GarfbotRespond) gapikey = config.GIF_TOKEN @@ -22,13 +21,15 @@ intents.message_content = True garfbot = discord.Client(intents=intents) garf_respond = GarfbotRespond() +garfield = GarfAI() +kroger = Kroger() @garfbot.event async def on_ready(): try: - asyncio.create_task(process_image_requests()) garf_respond.load_responses() + asyncio.create_task(garfield.process_image_requests()) logger.info(f"Logged in as {garfbot.user.name} running {txtmodel} and {imgmodel}.") except Exception as e: logger.error(e) @@ -48,21 +49,21 @@ async def on_message(message): return if lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): - question = content[12:] if lower.startswith("hey garfield") else message.content - answer = await generate_chat(question) - logger.info(f"Chat Request - User: {user}, Server: {guild}, Prompt: {question}") + prompt = content[12:] if lower.startswith("hey garfield") else message.content + answer = await garfield.generate_chat(prompt) + logger.info(f"Chat Request - User: {user}, Server: {guild}, Prompt: {prompt}") await message.channel.send(answer) if lower.startswith('garfpic '): prompt = content[8:] logger.info(f"Image Request - User: {user}, Server: {guild}, Prompt: {prompt}") await message.channel.send(f"`Please wait... image generation queued: {prompt}`") - await garfpic(message, prompt) + await garfield.garfpic(message, prompt) # Wikipedia if lower.startswith('garfwiki '): - search_term = message.content[9:] - summary = await wikisum(search_term) + query = message.content[9:] + summary = await garfield.wikisum(query) await message.channel.send(summary) # QR codes @@ -87,7 +88,7 @@ async def on_message(message): try: logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}") if is_private(target): - rejection = await generate_chat("Hey Garfield, explain to me why I am dumb for trying to hack your private computer network.") + rejection = await garfield.generate_chat("Hey Garfield, explain to me why I am dumb for trying to hack your private computer network.") await message.channel.send(rejection) else: result = subprocess.run(['ping', '-c', '4', target], capture_output=True, text=True) @@ -99,7 +100,7 @@ async def on_message(message): try: logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}") if is_private(target): - rejection = await generate_chat("Hey Garfield, explain to me why I am dumb for trying to hack your private computer network.") + rejection = await garfield.generate_chat("Hey Garfield, explain to me why I am dumb for trying to hack your private computer network.") await message.channel.send(rejection) else: result = subprocess.run(['nslookup', target], capture_output=True, text=True) @@ -111,7 +112,7 @@ async def on_message(message): try: logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}") if is_private(target): - rejection = await generate_chat("Hey Garfield, explain to me why I am dumb for trying to hack your private computer network.") + rejection = await garfield.generate_chat("Hey Garfield, explain to me why I am dumb for trying to hack your private computer network.") await message.channel.send(rejection) else: await message.channel.send(f"`Scanning {target}...`") @@ -123,21 +124,8 @@ async def on_message(message): # Kroger Shopping if lower.startswith("garfshop "): try: - kroken = kroger_token() - kroger_query = message.content.split() - product = " ".join(kroger_query[1:-1]) - zipcode = kroger_query[-1] - loc_data = find_store(zipcode, kroken) - loc_id = loc_data['data'][0]['locationId'] - store_name = loc_data['data'][0]['name'] - product_query = search_product(product, loc_id, kroken) - products = product_query['data'] - sorted_products = sorted(products, key=lambda item: item['items'][0]['price']['regular']) - response = f"Prices for `{product}` at `{store_name}` near `{zipcode}`:\n" - for item in sorted_products: - product_name = item['description'] - price = item['items'][0]['price']['regular'] - response += f"- `${price}`: {product_name} \n" + query = message.content[9:] + response = kroger.garfshop(query) await message.channel.send(response) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") diff --git a/garfpy/__init__.py b/garfpy/__init__.py index 353e5c8..ce62920 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -1,16 +1,10 @@ # garfpy/__init__.py from .log import logger -from .kroger import( - kroger_token, find_store, search_product -) -from .garfai import( - garfpic, - process_image_requests, - generate_chat -) +from .kroger import Kroger from .iputils import is_private from .aod import aod_message -from .wiki import wikisum from .qr import generate_qr +from .kroger import Kroger +from .garfai import GarfAI from .respond import GarfbotRespond \ No newline at end of file diff --git a/garfpy/garfai.py b/garfpy/garfai.py index 35e6f90..d9f3459 100644 --- a/garfpy/garfai.py +++ b/garfpy/garfai.py @@ -4,86 +4,95 @@ import config import aiohttp import asyncio import discord +import wikipedia from openai import AsyncOpenAI from garfpy import logger -openaikey = config.OPENAI_TOKEN -txtmodel = config.TXT_MODEL -imgmodel = config.IMG_MODEL +class GarfAI: + def __init__(self): + self.openaikey = config.OPENAI_TOKEN + self.txtmodel = config.TXT_MODEL + self.imgmodel = config.IMG_MODEL + self.image_request_queue = asyncio.Queue() -image_request_queue = asyncio.Queue() + async def garfpic(self, message, prompt): + await self.image_request_queue.put({'message': message, 'prompt': prompt}) -async def garfpic(message, prompt): - await image_request_queue.put({'message': message, 'prompt': prompt}) + async def generate_image(self, prompt): + try: + client = AsyncOpenAI(api_key = self.openaikey) + response = await client.images.generate( + model=self.imgmodel, + prompt=prompt, + n=1, + size="1024x1024" + ) + image_url = response.data[0].url + return image_url + except openai.BadRequestError as e: + return f"`GarfBot Error: ({e.status_code}) - Your request was rejected as a result of our safety system.`" + except openai.InternalServerError as e: + logger.error(e) + return f"`GarfBot Error: ({e.status_code}) - Monday`" + except Exception as e: + logger.error(e) + return f"`GarfBot Error: Lasagna`" -async def generate_image(prompt): - try: - client = AsyncOpenAI(api_key = openaikey) - response = await client.images.generate( - model=imgmodel, - prompt=prompt, - n=1, - size="1024x1024" - ) - image_url = response.data[0].url - return image_url - except openai.BadRequestError as e: - return f"`GarfBot Error: ({e.status_code}) - Your request was rejected as a result of our safety system.`" - except openai.InternalServerError as e: - logger.error(e) - return f"`GarfBot Error: ({e.status_code}) - Monday`" - except Exception as e: - logger.error(e) - return f"`GarfBot Error: Lasagna`" + async def process_image_requests(self): + async with aiohttp.ClientSession() as session: + while True: + request = await self.image_request_queue.get() + message = request['message'] + prompt = request['prompt'] + image_url = await self.generate_image(prompt) + if "GarfBot Error" not in image_url: + logger.info("Downloading & sending image...") + async with session.get(image_url) as resp: + if resp.status == 200: + image_data = await resp.read() + ram_image = io.BytesIO(image_data) + ram_image.seek(0) + timestamp = message.created_at.strftime('%Y%m%d%H%M%S') + filename = f"{timestamp}_generated_image.png" + sendfile = discord.File(fp=ram_image, filename=filename) + try: + await message.channel.send(file=sendfile) + except Exception as e: + logger.error(e) + else: + await message.channel.send("`GarfBot Error: Odie`") + else: + await message.channel.send(image_url) + self.image_request_queue.task_done() + await asyncio.sleep(2) -async def process_image_requests(): - async with aiohttp.ClientSession() as session: - while True: - request = await image_request_queue.get() - message = request['message'] - prompt = request['prompt'] - image_url = await generate_image(prompt) - if "GarfBot Error" not in image_url: - logger.info("Downloading & sending image...") - async with session.get(image_url) as resp: - if resp.status == 200: - image_data = await resp.read() - ram_image = io.BytesIO(image_data) - ram_image.seek(0) - timestamp = message.created_at.strftime('%Y%m%d%H%M%S') - filename = f"{timestamp}_generated_image.png" - sendfile = discord.File(fp=ram_image, filename=filename) - try: - await message.channel.send(file=sendfile) - except Exception as e: - logger.error(e) - else: - await message.channel.send("`GarfBot Error: Odie`") - else: - await message.channel.send(image_url) - image_request_queue.task_done() - await asyncio.sleep(2) + async def generate_chat(self, question): + try: + client = AsyncOpenAI(api_key = self.openaikey) + response = await client.chat.completions.create( + model=self.txtmodel, + messages=[ + {"role": "system", "content": "Pretend you are sarcastic Garfield."}, + {"role": "user", "content": f"{question}"} + ], + max_tokens=400 + ) + answer = response.choices[0].message.content + return answer.replace("an AI language model", "a cartoon animal") + except openai.BadRequestError as e: + return f"`GarfBot Error: {e}`" + except openai.APIError as e: + logger.info(e, flush=True) + return f"`GarfBot Error: Monday`" + except Exception as e: + logger.info(e, flush=True) + return f"`GarfBot Error: Lasagna`" -# GarfChats -async def generate_chat(question): - try: - client = AsyncOpenAI(api_key = openaikey) - response = await client.chat.completions.create( - model=txtmodel, - messages=[ - {"role": "system", "content": "Pretend you are sarcastic Garfield."}, - {"role": "user", "content": f"{question}"} - ], - max_tokens=400 - ) - answer = response.choices[0].message.content - return answer.replace("an AI language model", "a cartoon animal") - except openai.BadRequestError as e: - return f"`GarfBot Error: {e}`" - except openai.APIError as e: - logger.info(e, flush=True) - return f"`GarfBot Error: Monday`" - except Exception as e: - logger.info(e, flush=True) - return f"`GarfBot Error: Lasagna`" + async def wikisum(self, query): + try: + summary = wikipedia.summary(query) + garfsum = await self.generate_chat(f"Please summarize in your own words: {summary}") + return garfsum + except Exception as e: + return e \ No newline at end of file diff --git a/garfpy/kroger.py b/garfpy/kroger.py index 3d50aef..b126033 100644 --- a/garfpy/kroger.py +++ b/garfpy/kroger.py @@ -4,45 +4,67 @@ from base64 import b64encode from garfpy import logger -client_id = config.CLIENT_ID -client_secret = config.CLIENT_SECRET +class Kroger: + def __init__(self): + self.client_id = config.CLIENT_ID + self.client_secret = config.CLIENT_SECRET + self.auth = b64encode(f"{self.client_id}:{self.client_secret}".encode()).decode() -auth = b64encode(f"{client_id}:{client_secret}".encode()).decode() + def kroger_token(self): + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Authorization': f'Basic {self.auth}' + } -def kroger_token(): - headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': f'Basic {auth}' - } + response = requests.post('https://api.kroger.com/v1/connect/oauth2/token', headers=headers, data={ + 'grant_type': 'client_credentials', + 'scope': 'product.compact' + }) - response = requests.post('https://api.kroger.com/v1/connect/oauth2/token', headers=headers, data={ - 'grant_type': 'client_credentials', - 'scope': 'product.compact' - }) + response.raise_for_status() + return response.json()['access_token'] - response.raise_for_status() - return response.json()['access_token'] + def find_store(self, zipcode, kroken): + headers = { + 'Authorization': f'Bearer {kroken}', + } + params = { + 'filter.zipCode.near': zipcode, + 'filter.limit': 1, + } + response = requests.get('https://api.kroger.com/v1/locations', headers=headers, params=params) + return response.json() -def find_store(zipcode, kroken): - headers = { - 'Authorization': f'Bearer {kroken}', - } - params = { - 'filter.zipCode.near': zipcode, - 'filter.limit': 1, - } - response = requests.get('https://api.kroger.com/v1/locations', headers=headers, params=params) - return response.json() + def search_product(self, product, loc_id, kroken): + logger.info(f"Searching for {product}...") + headers = { + 'Authorization': f'Bearer {kroken}', + } + params = { + 'filter.term': product, + 'filter.locationId': loc_id, + 'filter.limit': 10 + } + response = requests.get('https://api.kroger.com/v1/products', headers=headers, params=params) + return response.json() -def search_product(product, loc_id, kroken): - logger.info(f"Searching for {product}...") - headers = { - 'Authorization': f'Bearer {kroken}', - } - params = { - 'filter.term': product, - 'filter.locationId': loc_id, - 'filter.limit': 10 - } - response = requests.get('https://api.kroger.com/v1/products', headers=headers, params=params) - return response.json() + def garfshop(self, query): + try: + query = query.split() + kroken = self.kroger_token() + product = query[-2] + zipcode = query[-1] + loc_data = self.find_store(zipcode, kroken) + loc_id = loc_data['data'][0]['locationId'] + store_name = loc_data['data'][0]['name'] + product_query = self.search_product(product, loc_id, kroken) + products = product_query['data'] + sorted_products = sorted(products, key=lambda item: item['items'][0]['price']['regular']) + response = f"Prices for `{product}` at `{store_name}` near `{zipcode}`:\n" + for item in sorted_products: + product_name = item['description'] + price = item['items'][0]['price']['regular'] + response += f"- `${price}`: {product_name} \n" + return response + except Exception as e: + return e diff --git a/garfpy/respond.py b/garfpy/respond.py index 4a28766..a7941e0 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -7,37 +7,37 @@ import re class GarfbotRespond: def __init__(self): - self.garfbot_guild_responses = {} + self.guild_responses = {} self.responses_file = 'responses.json' def load_responses(self): if os.path.exists(self.responses_file): try: with open(self.responses_file, 'r', encoding='utf-8') as f: - self.garfbot_guild_responses = json.load(f) - self.garfbot_guild_responses = {int(k): v for k, v in self.garfbot_guild_responses.items()} - total_responses = sum(len(responses) for responses in self.garfbot_guild_responses.values()) - logger.info(f"Loaded responses for {len(self.garfbot_guild_responses)} server(s), ({total_responses} total responses)") + self.guild_responses = json.load(f) + self.guild_responses = {int(k): v for k, v in self.guild_responses.items()} + total_responses = sum(len(responses) for responses in self.guild_responses.values()) + logger.info(f"Loaded responses for {len(self.guild_responses)} server(s), ({total_responses} total responses)") except Exception as e: logger.info(f"Error loading responses: {e}") - self.garfbot_guild_responses = {} + self.guild_responses = {} else: - self.garfbot_guild_responses = {} + self.guild_responses = {} def save_responses(self): try: - save_data = {str(k): v for k, v in self.garfbot_guild_responses.items()} + save_data = {str(k): v for k, v in self.guild_responses.items()} with open(self.responses_file, 'w', encoding='utf-8') as f: json.dump(save_data, f, indent=2, ensure_ascii=False) - total_responses = sum(len(responses) for responses in self.garfbot_guild_responses.values()) - logger.info(f"Saved responses for {len(self.garfbot_guild_responses)} servers ({total_responses} total responses)") + total_responses = sum(len(responses) for responses in self.guild_responses.values()) + logger.info(f"Saved responses for {len(self.guild_responses)} servers ({total_responses} total responses)") except Exception as e: logger.info(f"Error saving responses: {e}") def get_responses(self, guild_id): - if guild_id not in self.garfbot_guild_responses: - self.garfbot_guild_responses[guild_id] = {} - return self.garfbot_guild_responses[guild_id] + if guild_id not in self.guild_responses: + self.guild_responses[guild_id] = {} + return self.guild_responses[guild_id] async def garfbot_response(self, message, content): guild_id = message.guild.id @@ -86,7 +86,7 @@ class GarfbotRespond: responses = self.get_responses(guild_id) responses[trigger] = response_text - self.garfbot_guild_responses[guild_id] = responses + self.guild_responses[guild_id] = responses self.save_responses() embed = discord.Embed( @@ -105,7 +105,7 @@ class GarfbotRespond: if trigger in responses: removed_response = responses[trigger] del responses[trigger] - self.garfbot_guild_responses[guild_id] = responses + self.guild_responses[guild_id] = responses self.save_responses() embed = discord.Embed( @@ -123,7 +123,7 @@ class GarfbotRespond: if key.lower() == trigger.lower(): removed_response = responses[key] del responses[key] - self.garfbot_guild_responses[guild_id] = responses + self.guild_responses[guild_id] = responses self.save_responses() embed = discord.Embed( diff --git a/garfpy/wiki.py b/garfpy/wiki.py deleted file mode 100644 index 0904bc4..0000000 --- a/garfpy/wiki.py +++ /dev/null @@ -1,12 +0,0 @@ -import wikipedia -from garfpy import generate_chat - -async def wikisum(search_term): - try: - summary = wikipedia.summary(search_term) - garfsum = await generate_chat(f"Please summarize in your own words: {summary}") - - return garfsum - - except Exception as e: - return e \ No newline at end of file