Compare commits

..

3 Commits

Author SHA1 Message Date
d3eb82f1bd Merge pull request 'refactor-cleanup' (#1) from refactor-cleanup into main
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 5s
Reviewed-on: #1
2025-06-05 18:34:22 +00:00
353284b2c3 looks good 2025-06-05 13:33:14 -05:00
82561f050f getting classy 2025-06-04 21:36:43 -05:00
6 changed files with 176 additions and 175 deletions

View File

@ -5,9 +5,8 @@ import subprocess
from garfpy import( from garfpy import(
logger, is_private, logger, is_private,
kroger_token, find_store, search_product, aod_message, generate_qr,
garfpic, process_image_requests, generate_chat, Kroger, GarfAI, GarfbotRespond)
aod_message, wikisum, generate_qr, GarfbotRespond)
gapikey = config.GIF_TOKEN gapikey = config.GIF_TOKEN
@ -22,13 +21,15 @@ intents.message_content = True
garfbot = discord.Client(intents=intents) garfbot = discord.Client(intents=intents)
garf_respond = GarfbotRespond() garf_respond = GarfbotRespond()
garfield = GarfAI()
kroger = Kroger()
@garfbot.event @garfbot.event
async def on_ready(): async def on_ready():
try: try:
asyncio.create_task(process_image_requests())
garf_respond.load_responses() garf_respond.load_responses()
asyncio.create_task(garfield.process_image_requests())
logger.info(f"Logged in as {garfbot.user.name} running {txtmodel} and {imgmodel}.") logger.info(f"Logged in as {garfbot.user.name} running {txtmodel} and {imgmodel}.")
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
@ -48,21 +49,21 @@ async def on_message(message):
return return
if lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): if lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel):
question = content[12:] if lower.startswith("hey garfield") else message.content prompt = content[12:] if lower.startswith("hey garfield") else message.content
answer = await generate_chat(question) answer = await garfield.generate_chat(prompt)
logger.info(f"Chat Request - User: {user}, Server: {guild}, Prompt: {question}") logger.info(f"Chat Request - User: {user}, Server: {guild}, Prompt: {prompt}")
await message.channel.send(answer) await message.channel.send(answer)
if lower.startswith('garfpic '): if lower.startswith('garfpic '):
prompt = content[8:] prompt = content[8:]
logger.info(f"Image Request - User: {user}, Server: {guild}, Prompt: {prompt}") logger.info(f"Image Request - User: {user}, Server: {guild}, Prompt: {prompt}")
await message.channel.send(f"`Please wait... image generation queued: {prompt}`") await message.channel.send(f"`Please wait... image generation queued: {prompt}`")
await garfpic(message, prompt) await garfield.garfpic(message, prompt)
# Wikipedia # Wikipedia
if lower.startswith('garfwiki '): if lower.startswith('garfwiki '):
search_term = message.content[9:] query = message.content[9:]
summary = await wikisum(search_term) summary = await garfield.wikisum(query)
await message.channel.send(summary) await message.channel.send(summary)
# QR codes # QR codes
@ -87,7 +88,7 @@ async def on_message(message):
try: try:
logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}") logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}")
if is_private(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) await message.channel.send(rejection)
else: else:
result = subprocess.run(['ping', '-c', '4', target], capture_output=True, text=True) result = subprocess.run(['ping', '-c', '4', target], capture_output=True, text=True)
@ -99,7 +100,7 @@ async def on_message(message):
try: try:
logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}") logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}")
if is_private(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) await message.channel.send(rejection)
else: else:
result = subprocess.run(['nslookup', target], capture_output=True, text=True) result = subprocess.run(['nslookup', target], capture_output=True, text=True)
@ -111,7 +112,7 @@ async def on_message(message):
try: try:
logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}") logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}")
if is_private(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) await message.channel.send(rejection)
else: else:
await message.channel.send(f"`Scanning {target}...`") await message.channel.send(f"`Scanning {target}...`")
@ -123,21 +124,8 @@ async def on_message(message):
# Kroger Shopping # Kroger Shopping
if lower.startswith("garfshop "): if lower.startswith("garfshop "):
try: try:
kroken = kroger_token() query = message.content[9:]
kroger_query = message.content.split() response = kroger.garfshop(query)
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"
await message.channel.send(response) await message.channel.send(response)
except Exception as e: except Exception as e:
await message.channel.send(f"`GarfBot Error: {str(e)}`") await message.channel.send(f"`GarfBot Error: {str(e)}`")

View File

@ -1,16 +1,10 @@
# garfpy/__init__.py # garfpy/__init__.py
from .log import logger from .log import logger
from .kroger import( from .kroger import Kroger
kroger_token, find_store, search_product
)
from .garfai import(
garfpic,
process_image_requests,
generate_chat
)
from .iputils import is_private from .iputils import is_private
from .aod import aod_message from .aod import aod_message
from .wiki import wikisum
from .qr import generate_qr from .qr import generate_qr
from .kroger import Kroger
from .garfai import GarfAI
from .respond import GarfbotRespond from .respond import GarfbotRespond

View File

@ -4,86 +4,95 @@ import config
import aiohttp import aiohttp
import asyncio import asyncio
import discord import discord
import wikipedia
from openai import AsyncOpenAI from openai import AsyncOpenAI
from garfpy import logger from garfpy import logger
openaikey = config.OPENAI_TOKEN
txtmodel = config.TXT_MODEL class GarfAI:
imgmodel = config.IMG_MODEL 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): async def generate_image(self, prompt):
await image_request_queue.put({'message': message, 'prompt': 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): async def process_image_requests(self):
try: async with aiohttp.ClientSession() as session:
client = AsyncOpenAI(api_key = openaikey) while True:
response = await client.images.generate( request = await self.image_request_queue.get()
model=imgmodel, message = request['message']
prompt=prompt, prompt = request['prompt']
n=1, image_url = await self.generate_image(prompt)
size="1024x1024" if "GarfBot Error" not in image_url:
) logger.info("Downloading & sending image...")
image_url = response.data[0].url async with session.get(image_url) as resp:
return image_url if resp.status == 200:
except openai.BadRequestError as e: image_data = await resp.read()
return f"`GarfBot Error: ({e.status_code}) - Your request was rejected as a result of our safety system.`" ram_image = io.BytesIO(image_data)
except openai.InternalServerError as e: ram_image.seek(0)
logger.error(e) timestamp = message.created_at.strftime('%Y%m%d%H%M%S')
return f"`GarfBot Error: ({e.status_code}) - Monday`" filename = f"{timestamp}_generated_image.png"
except Exception as e: sendfile = discord.File(fp=ram_image, filename=filename)
logger.error(e) try:
return f"`GarfBot Error: Lasagna`" 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 def generate_chat(self, question):
async with aiohttp.ClientSession() as session: try:
while True: client = AsyncOpenAI(api_key = self.openaikey)
request = await image_request_queue.get() response = await client.chat.completions.create(
message = request['message'] model=self.txtmodel,
prompt = request['prompt'] messages=[
image_url = await generate_image(prompt) {"role": "system", "content": "Pretend you are sarcastic Garfield."},
if "GarfBot Error" not in image_url: {"role": "user", "content": f"{question}"}
logger.info("Downloading & sending image...") ],
async with session.get(image_url) as resp: max_tokens=400
if resp.status == 200: )
image_data = await resp.read() answer = response.choices[0].message.content
ram_image = io.BytesIO(image_data) return answer.replace("an AI language model", "a cartoon animal")
ram_image.seek(0) except openai.BadRequestError as e:
timestamp = message.created_at.strftime('%Y%m%d%H%M%S') return f"`GarfBot Error: {e}`"
filename = f"{timestamp}_generated_image.png" except openai.APIError as e:
sendfile = discord.File(fp=ram_image, filename=filename) logger.info(e, flush=True)
try: return f"`GarfBot Error: Monday`"
await message.channel.send(file=sendfile) except Exception as e:
except Exception as e: logger.info(e, flush=True)
logger.error(e) return f"`GarfBot Error: Lasagna`"
else:
await message.channel.send("`GarfBot Error: Odie`")
else:
await message.channel.send(image_url)
image_request_queue.task_done()
await asyncio.sleep(2)
# GarfChats async def wikisum(self, query):
async def generate_chat(question): try:
try: summary = wikipedia.summary(query)
client = AsyncOpenAI(api_key = openaikey) garfsum = await self.generate_chat(f"Please summarize in your own words: {summary}")
response = await client.chat.completions.create( return garfsum
model=txtmodel, except Exception as e:
messages=[ return e
{"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`"

View File

@ -4,45 +4,67 @@ from base64 import b64encode
from garfpy import logger from garfpy import logger
client_id = config.CLIENT_ID class Kroger:
client_secret = config.CLIENT_SECRET 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(): response = requests.post('https://api.kroger.com/v1/connect/oauth2/token', headers=headers, data={
headers = { 'grant_type': 'client_credentials',
'Content-Type': 'application/x-www-form-urlencoded', 'scope': 'product.compact'
'Authorization': f'Basic {auth}' })
}
response = requests.post('https://api.kroger.com/v1/connect/oauth2/token', headers=headers, data={ response.raise_for_status()
'grant_type': 'client_credentials', return response.json()['access_token']
'scope': 'product.compact'
})
response.raise_for_status() def find_store(self, zipcode, kroken):
return response.json()['access_token'] 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): def search_product(self, product, loc_id, kroken):
headers = { logger.info(f"Searching for {product}...")
'Authorization': f'Bearer {kroken}', headers = {
} 'Authorization': f'Bearer {kroken}',
params = { }
'filter.zipCode.near': zipcode, params = {
'filter.limit': 1, 'filter.term': product,
} 'filter.locationId': loc_id,
response = requests.get('https://api.kroger.com/v1/locations', headers=headers, params=params) 'filter.limit': 10
return response.json() }
response = requests.get('https://api.kroger.com/v1/products', headers=headers, params=params)
return response.json()
def search_product(product, loc_id, kroken): def garfshop(self, query):
logger.info(f"Searching for {product}...") try:
headers = { query = query.split()
'Authorization': f'Bearer {kroken}', kroken = self.kroger_token()
} product = query[-2]
params = { zipcode = query[-1]
'filter.term': product, loc_data = self.find_store(zipcode, kroken)
'filter.locationId': loc_id, loc_id = loc_data['data'][0]['locationId']
'filter.limit': 10 store_name = loc_data['data'][0]['name']
} product_query = self.search_product(product, loc_id, kroken)
response = requests.get('https://api.kroger.com/v1/products', headers=headers, params=params) products = product_query['data']
return response.json() 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

View File

@ -7,37 +7,37 @@ import re
class GarfbotRespond: class GarfbotRespond:
def __init__(self): def __init__(self):
self.garfbot_guild_responses = {} self.guild_responses = {}
self.responses_file = 'responses.json' self.responses_file = 'responses.json'
def load_responses(self): def load_responses(self):
if os.path.exists(self.responses_file): if os.path.exists(self.responses_file):
try: try:
with open(self.responses_file, 'r', encoding='utf-8') as f: with open(self.responses_file, 'r', encoding='utf-8') as f:
self.garfbot_guild_responses = json.load(f) self.guild_responses = json.load(f)
self.garfbot_guild_responses = {int(k): v for k, v in self.garfbot_guild_responses.items()} self.guild_responses = {int(k): v for k, v in self.guild_responses.items()}
total_responses = sum(len(responses) for responses in self.garfbot_guild_responses.values()) total_responses = sum(len(responses) for responses in self.guild_responses.values())
logger.info(f"Loaded responses for {len(self.garfbot_guild_responses)} server(s), ({total_responses} total responses)") logger.info(f"Loaded responses for {len(self.guild_responses)} server(s), ({total_responses} total responses)")
except Exception as e: except Exception as e:
logger.info(f"Error loading responses: {e}") logger.info(f"Error loading responses: {e}")
self.garfbot_guild_responses = {} self.guild_responses = {}
else: else:
self.garfbot_guild_responses = {} self.guild_responses = {}
def save_responses(self): def save_responses(self):
try: 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: with open(self.responses_file, 'w', encoding='utf-8') as f:
json.dump(save_data, f, indent=2, ensure_ascii=False) json.dump(save_data, f, indent=2, ensure_ascii=False)
total_responses = sum(len(responses) for responses in self.garfbot_guild_responses.values()) total_responses = sum(len(responses) for responses in self.guild_responses.values())
logger.info(f"Saved responses for {len(self.garfbot_guild_responses)} servers ({total_responses} total responses)") logger.info(f"Saved responses for {len(self.guild_responses)} servers ({total_responses} total responses)")
except Exception as e: except Exception as e:
logger.info(f"Error saving responses: {e}") logger.info(f"Error saving responses: {e}")
def get_responses(self, guild_id): def get_responses(self, guild_id):
if guild_id not in self.garfbot_guild_responses: if guild_id not in self.guild_responses:
self.garfbot_guild_responses[guild_id] = {} self.guild_responses[guild_id] = {}
return self.garfbot_guild_responses[guild_id] return self.guild_responses[guild_id]
async def garfbot_response(self, message, content): async def garfbot_response(self, message, content):
guild_id = message.guild.id guild_id = message.guild.id
@ -86,7 +86,7 @@ class GarfbotRespond:
responses = self.get_responses(guild_id) responses = self.get_responses(guild_id)
responses[trigger] = response_text responses[trigger] = response_text
self.garfbot_guild_responses[guild_id] = responses self.guild_responses[guild_id] = responses
self.save_responses() self.save_responses()
embed = discord.Embed( embed = discord.Embed(
@ -105,7 +105,7 @@ class GarfbotRespond:
if trigger in responses: if trigger in responses:
removed_response = responses[trigger] removed_response = responses[trigger]
del responses[trigger] del responses[trigger]
self.garfbot_guild_responses[guild_id] = responses self.guild_responses[guild_id] = responses
self.save_responses() self.save_responses()
embed = discord.Embed( embed = discord.Embed(
@ -123,7 +123,7 @@ class GarfbotRespond:
if key.lower() == trigger.lower(): if key.lower() == trigger.lower():
removed_response = responses[key] removed_response = responses[key]
del responses[key] del responses[key]
self.garfbot_guild_responses[guild_id] = responses self.guild_responses[guild_id] = responses
self.save_responses() self.save_responses()
embed = discord.Embed( embed = discord.Embed(

View File

@ -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