From b4ca6b778d992e8ba048084f6225fe02023f1d26 Mon Sep 17 00:00:00 2001 From: crate Date: Tue, 3 Jun 2025 20:33:55 -0500 Subject: [PATCH 01/21] add auto responses --- .gitignore | 3 + garfmain.py | 25 +++++-- garfpy/__init__.py | 4 +- garfpy/respond.py | 168 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 garfpy/respond.py diff --git a/.gitignore b/.gitignore index 6c33acd..0b96242 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ garfpy/__pycache__/ *.old *.log* meows.py +meow_counts.json +user_stats.json +responses.json \ No newline at end of file diff --git a/garfmain.py b/garfmain.py index 951ced6..4df5b85 100644 --- a/garfmain.py +++ b/garfmain.py @@ -1,3 +1,5 @@ +import os +import json import config import asyncio import discord @@ -7,7 +9,7 @@ from garfpy import( logger, is_private, kroger_token, find_store, search_product, garfpic, process_image_requests, generate_chat, - aod_message, wikisum, generate_qr) + aod_message, wikisum, generate_qr, GarfbotRespond) gapikey = config.GIF_TOKEN @@ -21,16 +23,17 @@ intents.messages = True intents.message_content = True garfbot = discord.Client(intents=intents) +garf_respond = GarfbotRespond @garfbot.event async def on_ready(): try: asyncio.create_task(process_image_requests()) + garf_respond.load_responses() logger.info(f"Logged in as {garfbot.user.name} running {txtmodel} and {imgmodel}.") except Exception as e: logger.error(e) - @garfbot.event async def on_message(message): if message.author == garfbot.user: @@ -60,7 +63,7 @@ async def on_message(message): if message.content.lower().startswith('garfqr '): text = message.content[7:] if len(text) > 1000: - await mesage.channel.send("❌ Text too long! Maximum 1000 characters.") + await message.channel.send("❌ Text too long! Maximum 1000 characters.") else: try: qr_code = await generate_qr(text) @@ -143,9 +146,23 @@ async def on_message(message): # Army of Dawn Server only!! if message.guild and message.guild.id == 719605634772893757: - await aod_message(garfbot, message) + # Auto-responses + guild_id = message.guild.id + content = message.content.strip() + content_lower = content.lower() + responses = garf_respond.get_responses(guild_id) + + if content.lower().startswith('garfbot response'): + await garf_respond.garfbot_response(message, content) + return + + for trigger, response in responses.items(): + if trigger.lower() in content_lower: + await message.channel.send(response) + break + async def garfbot_connect(): while True: diff --git a/garfpy/__init__.py b/garfpy/__init__.py index e447804..353e5c8 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -1,4 +1,5 @@ # garfpy/__init__.py + from .log import logger from .kroger import( kroger_token, find_store, search_product @@ -11,4 +12,5 @@ from .garfai import( from .iputils import is_private from .aod import aod_message from .wiki import wikisum -from .qr import generate_qr \ No newline at end of file +from .qr import generate_qr +from .respond import GarfbotRespond \ No newline at end of file diff --git a/garfpy/respond.py b/garfpy/respond.py new file mode 100644 index 0000000..3cb57c2 --- /dev/null +++ b/garfpy/respond.py @@ -0,0 +1,168 @@ +from garfpy import logger +import discord +import json +import os +import re + + +class GarfbotRespond: + def __init__(self): + self.garfbot_guild_responses = {} + self.responses_file = 'responses.json' + + def load_responses(self): + global garfbot_guild_responses + if os.path.exists(self.responses_file): + try: + with open(self.responses_file, 'r', encoding='utf-8') as f: + garfbot_guild_responses = json.load(f) + garfbot_guild_responses = {int(k): v for k, v in garfbot_guild_responses.items()} + total_responses = sum(len(responses) for responses in garfbot_guild_responses.values()) + logger.info(f"Loaded responses for {len(garfbot_guild_responses)} server(s), ({total_responses} total responses)") + except Exception as e: + logger.info(f"Error loading responses: {e}") + garfbot_guild_responses = {} + else: + garfbot_guild_responses = {} + + def save_responses(self): + try: + save_data = {str(k): v for k, v in garfbot_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 garfbot_guild_responses.values()) + logger.info(f"Saved responses for {len(garfbot_guild_responses)} servers ({total_responses} total responses)") + except Exception as e: + logger.info(f"Error saving responses: {e}") + + def get_responses(guild_id): + if guild_id not in garfbot_guild_responses: + garfbot_guild_responses[guild_id] = {} + return garfbot_guild_responses[guild_id] + + async def garfbot_response(self, message, content): + guild_id = message.guild.id + + add_pattern = r'garfbot\s+response\s+add\s+["\']([^"\']+)["\']\s+["\']([^"\']+)["\']' + add_match = re.search(add_pattern, content, re.IGNORECASE) + + if add_match: + trigger = add_match.group(1) + response_text = add_match.group(2) + await self.add_response(message, guild_id, trigger, response_text) + return + + add_simple_pattern = r'garfbot\s+response\s+add\s+(\S+)\s+(\S+)' + add_simple_match = re.search(add_simple_pattern, content, re.IGNORECASE) + + if add_simple_match: + trigger = add_simple_match.group(1) + response_text = add_simple_match.group(2) + await self.add_response(message, guild_id, trigger, response_text) + return + + remove_pattern = r'garfbot\s+response\s+remove\s+(\S+)' + remove_match = re.search(remove_pattern, content, re.IGNORECASE) + + if remove_match: + trigger = remove_match.group(1).strip() + await self.remove_response(message, guild_id, trigger) + return + + if re.search(r'garfbot\s+response\s+list', content, re.IGNORECASE): + await self.list_responses(message, guild_id) + return + + await message.channel.send( + "**Garfbot Auto-Response Commands:**\n" + "`garfbot response add \"trigger\" \"response\"`\n" + "`garfbot response remove \"trigger\"`\n" + "`garfbot response list`\n\n" + "**Examples:**\n" + "`garfbot response add \"hi\" \"Hello there!\"`\n" + "`garfbot response add \"that's what\" \"That's what she said!\"`\n" + "`garfbot response remove \"hi\"`" + ) + + async def add_response(self, message, guild_id, trigger, response_text): + if not response_text or not trigger: + await message.channel.send("❌ Trigger and response must not be null.") + return + + responses = self.get_responses(guild_id) + responses[trigger] = response_text + garfbot_guild_responses[guild_id] = responses + self.save_responses() + + embed = discord.Embed( + title="✅ Auto-response Added.", + color=0x00ff00 + ) + embed.add_field(name="Trigger", value=f"`{trigger}`", inline=True) + embed.add_field(name="Response", value=f"`{response_text}`", inline=True) + embed.set_footer(text=f"Server: {message.guild.name}") + + await message.channel.send(embed=embed) + + async def remove_response(self, message, guild_id, trigger): + responses = self.get_responses(guild_id) + + if trigger in responses: + removed_response = responses[trigger] + del responses[trigger] + garfbot_guild_responses[guild_id] = responses + self.save_responses() + + embed = discord.Embed( + title="✅ Auto-response Removed.", + color=0xff6b6b + ) + embed.add_field(name="Trigger", value=f"`{trigger}`", inline=True) + embed.add_field(name="Response", value=f"`{removed_response}`", inline=True) + embed.set_footer(text=f"Server: {message.guild.name}") + + await message.channel.send(embed=embed) + return + + for key in responses.keys(): + if key.lower() == trigger.lower(): + removed_response = responses[key] + del responses[key] + garfbot_guild_responses[guild_id] = responses + self.save_responses() + + embed = discord.Embed( + title="✅ Auto-response Removed.", + color=0xff6b6b + ) + embed.add_field(name="Trigger", value=f"`{key}`", inline=True) + embed.add_field(name="Response", value=f"`{removed_response}`", inline=True) + embed.set_footer(text=f"Server: {message.guild.name}") + + await message.channel.send(embed=embed) + return + + await message.channel.send(f"❌ No auto-response found for trigger: `{trigger}`") + + async def list_responses(self, message, guild_id): + responses = self.get_responses(guild_id) + + if not responses: + await message.channel.send("No auto-responses configured for this server.") + return + + embed = discord.Embed( + title=f"Auto-Responses for {message.guild.name}", + color=0x3498db + ) + + for i, (trigger, response) in enumerate(responses.items(), 1): + display_response = response[:50] + "..." if len(response) > 50 else response + embed.add_field( + name=f"{i}. `{trigger}`", + value=f"→ {display_response}", + inline=False + ) + + embed.set_footer(text=f"Total responses: {len(responses)}") + await message.channel.send(embed=embed) From 4226ce86c8b0cecff49b401e1f14dcbad559818c Mon Sep 17 00:00:00 2001 From: crate Date: Tue, 3 Jun 2025 20:41:49 -0500 Subject: [PATCH 02/21] fix GarfbotRespond instance --- garfmain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/garfmain.py b/garfmain.py index 4df5b85..63efe58 100644 --- a/garfmain.py +++ b/garfmain.py @@ -23,7 +23,7 @@ intents.messages = True intents.message_content = True garfbot = discord.Client(intents=intents) -garf_respond = GarfbotRespond +garf_respond = GarfbotRespond() @garfbot.event async def on_ready(): From 49831aeb47e12d37fd0e8d1a15bd5463a71e93d6 Mon Sep 17 00:00:00 2001 From: crate Date: Tue, 3 Jun 2025 20:55:41 -0500 Subject: [PATCH 03/21] response log --- garfmain.py | 2 +- garfpy/respond.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/garfmain.py b/garfmain.py index 63efe58..a9550d3 100644 --- a/garfmain.py +++ b/garfmain.py @@ -154,7 +154,7 @@ async def on_message(message): content_lower = content.lower() responses = garf_respond.get_responses(guild_id) - if content.lower().startswith('garfbot response'): + if content_lower.startswith('garfbot response'): await garf_respond.garfbot_response(message, content) return diff --git a/garfpy/respond.py b/garfpy/respond.py index 3cb57c2..81756ee 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -42,6 +42,8 @@ class GarfbotRespond: async def garfbot_response(self, message, content): guild_id = message.guild.id + + logger.info(content) add_pattern = r'garfbot\s+response\s+add\s+["\']([^"\']+)["\']\s+["\']([^"\']+)["\']' add_match = re.search(add_pattern, content, re.IGNORECASE) From d70584d4f8b3a015209e4bbfd155bf81fc8a1bcb Mon Sep 17 00:00:00 2001 From: crate Date: Tue, 3 Jun 2025 20:57:39 -0500 Subject: [PATCH 04/21] log test --- garfpy/respond.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/garfpy/respond.py b/garfpy/respond.py index 81756ee..cc725db 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -43,7 +43,7 @@ class GarfbotRespond: async def garfbot_response(self, message, content): guild_id = message.guild.id - logger.info(content) + logger.info(message.content) add_pattern = r'garfbot\s+response\s+add\s+["\']([^"\']+)["\']\s+["\']([^"\']+)["\']' add_match = re.search(add_pattern, content, re.IGNORECASE) From 0cb5f8fa386d43216d296bb3cd88710eb0b58010 Mon Sep 17 00:00:00 2001 From: crate Date: Wed, 4 Jun 2025 02:22:19 -0500 Subject: [PATCH 05/21] implement auto response --- garfmain.py | 25 +++++++++++++------------ garfpy/respond.py | 33 ++++++++++++++++----------------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/garfmain.py b/garfmain.py index a9550d3..5feaa7f 100644 --- a/garfmain.py +++ b/garfmain.py @@ -149,19 +149,20 @@ async def on_message(message): await aod_message(garfbot, message) # Auto-responses - guild_id = message.guild.id - content = message.content.strip() - content_lower = content.lower() - responses = garf_respond.get_responses(guild_id) - - if content_lower.startswith('garfbot response'): - await garf_respond.garfbot_response(message, content) - return + if message.guild: + guild_id = message.guild.id + content = message.content + content_lower = content.lower() + responses = garf_respond.get_responses(guild_id) - for trigger, response in responses.items(): - if trigger.lower() in content_lower: - await message.channel.send(response) - break + if message.content.lower().startswith('garfbot response '): + await garf_respond.garfbot_response(message, content) + return + + for trigger, response in responses.items(): + if trigger.lower() in content_lower: + await message.channel.send(response) + break async def garfbot_connect(): diff --git a/garfpy/respond.py b/garfpy/respond.py index cc725db..89ebe37 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -11,34 +11,33 @@ class GarfbotRespond: self.responses_file = 'responses.json' def load_responses(self): - global garfbot_guild_responses if os.path.exists(self.responses_file): try: with open(self.responses_file, 'r', encoding='utf-8') as f: - garfbot_guild_responses = json.load(f) - garfbot_guild_responses = {int(k): v for k, v in garfbot_guild_responses.items()} - total_responses = sum(len(responses) for responses in garfbot_guild_responses.values()) - logger.info(f"Loaded responses for {len(garfbot_guild_responses)} server(s), ({total_responses} total responses)") + 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)") except Exception as e: logger.info(f"Error loading responses: {e}") - garfbot_guild_responses = {} + self.garfbot_guild_responses = {} else: - garfbot_guild_responses = {} + self.garfbot_guild_responses = {} def save_responses(self): try: - save_data = {str(k): v for k, v in garfbot_guild_responses.items()} + save_data = {str(k): v for k, v in self.garfbot_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 garfbot_guild_responses.values()) - logger.info(f"Saved responses for {len(garfbot_guild_responses)} servers ({total_responses} total responses)") + 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)") except Exception as e: logger.info(f"Error saving responses: {e}") - def get_responses(guild_id): - if guild_id not in garfbot_guild_responses: - garfbot_guild_responses[guild_id] = {} - return garfbot_guild_responses[guild_id] + 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] async def garfbot_response(self, message, content): guild_id = message.guild.id @@ -93,7 +92,7 @@ class GarfbotRespond: responses = self.get_responses(guild_id) responses[trigger] = response_text - garfbot_guild_responses[guild_id] = responses + self.garfbot_guild_responses[guild_id] = responses self.save_responses() embed = discord.Embed( @@ -112,7 +111,7 @@ class GarfbotRespond: if trigger in responses: removed_response = responses[trigger] del responses[trigger] - garfbot_guild_responses[guild_id] = responses + self.garfbot_guild_responses[guild_id] = responses self.save_responses() embed = discord.Embed( @@ -130,7 +129,7 @@ class GarfbotRespond: if key.lower() == trigger.lower(): removed_response = responses[key] del responses[key] - garfbot_guild_responses[guild_id] = responses + self.garfbot_guild_responses[guild_id] = responses self.save_responses() embed = discord.Embed( From d707398c2643d0197ae0958ba3c5c3747ead509e Mon Sep 17 00:00:00 2001 From: crate Date: Wed, 4 Jun 2025 02:33:37 -0500 Subject: [PATCH 06/21] some cleanup on main --- garfmain.py | 69 +++++++++++++++++++++++++---------------------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/garfmain.py b/garfmain.py index 5feaa7f..e977e7e 100644 --- a/garfmain.py +++ b/garfmain.py @@ -25,6 +25,7 @@ garfbot = discord.Client(intents=intents) garf_respond = GarfbotRespond() + @garfbot.event async def on_ready(): try: @@ -34,33 +35,40 @@ async def on_ready(): except Exception as e: logger.error(e) + @garfbot.event async def on_message(message): + + content = message.content + content_lower = content.lower() + user = message.author.name + guild = message.guild.name if message.guild else "Direct Message" + guild_id = message.guild.id + + # Chats & pics if message.author == garfbot.user: return - if message.content.lower().startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): - user = message.author.name - server = message.guild.name if message.guild else "Direct Message" - question = message.content[12:] if message.content.lower().startswith("hey garfield") else message.content + if content_lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): + question = content[12:] if content_lower.startswith("hey garfield") else message.content answer = await generate_chat(question) - logger.info(f"Chat Request - User: {user}, Server: {server}, Prompt: {question}") + logger.info(f"Chat Request - User: {user}, Server: {guild}, Prompt: {question}") await message.channel.send(answer) - if message.content.lower().startswith('garfpic '): - user = message.author.name - server = message.guild.name if message.guild else "Direct Message" - prompt = message.content[8:] - logger.info(f"Image Request - User: {user}, Server: {server}, Prompt: {prompt}") + if content_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) - if message.content.lower().startswith('garfwiki '): + # Wikipedia + if content_lower.startswith('garfwiki '): search_term = message.content[9:] summary = await wikisum(search_term) await message.channel.send(summary) - if message.content.lower().startswith('garfqr '): + # QR codes + if content_lower.startswith('garfqr '): text = message.content[7:] if len(text) > 1000: await message.channel.send("❌ Text too long! Maximum 1000 characters.") @@ -73,13 +81,13 @@ async def on_message(message): logger.error(e) await message.channel.send(e) - if message.content.lower().startswith("garfping "): + # IP utils + query = message.content.split() + target = query[-1] + + if content_lower.startswith("garfping "): try: - query = message.content.split() - user = message.author.name - server = message.guild.name if message.guild else "Direct Message" - target = query[-1] - logger.info(f"Ping Request - User: {user}, Server: {server}, Target: {target}") + 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.") await message.channel.send(rejection) @@ -89,13 +97,9 @@ async def on_message(message): except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") - if message.content.lower().startswith("garfdns "): + if content_lower.startswith("garfdns "): try: - query = message.content.split() - user = message.author.name - server = message.guild.name if message.guild else "Direct Message" - target = query[-1] - logger.info(f"NSLookup Request - User: {user}, Server: {server}, Target: {target}") + 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.") await message.channel.send(rejection) @@ -105,13 +109,9 @@ async def on_message(message): except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") - if message.content.lower().startswith("garfhack "): + if content_lower.startswith("garfhack "): try: - query = message.content.split() - user = message.author.name - server = message.guild.name if message.guild else "Direct Message" - target = query[-1] - logger.info(f"Nmap Request - User: {user}, Server: {server}, Target: {target}") + 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.") await message.channel.send(rejection) @@ -123,7 +123,7 @@ async def on_message(message): await message.channel.send(f"`GarfBot Error: {str(e)}`") # Kroger Shopping - if message.content.lower().startswith("garfshop "): + if content_lower.startswith("garfshop "): try: kroken = kroger_token() kroger_query = message.content.split() @@ -150,12 +150,9 @@ async def on_message(message): # Auto-responses if message.guild: - guild_id = message.guild.id - content = message.content - content_lower = content.lower() responses = garf_respond.get_responses(guild_id) - if message.content.lower().startswith('garfbot response '): + if content_lower.startswith('garfbot response '): await garf_respond.garfbot_response(message, content) return @@ -164,7 +161,7 @@ async def on_message(message): await message.channel.send(response) break - +# Run Garfbot async def garfbot_connect(): while True: try: From d9ac0cc36e5a2f3ff51fef9b5b33e1a4cf209cc7 Mon Sep 17 00:00:00 2001 From: crate Date: Wed, 4 Jun 2025 02:55:49 -0500 Subject: [PATCH 07/21] some cleanup etc --- garfmain.py | 28 +++++++++++++--------------- garfpy/respond.py | 30 ++++++++++++------------------ 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/garfmain.py b/garfmain.py index e977e7e..bf0e754 100644 --- a/garfmain.py +++ b/garfmain.py @@ -1,5 +1,3 @@ -import os -import json import config import asyncio import discord @@ -39,8 +37,8 @@ async def on_ready(): @garfbot.event async def on_message(message): - content = message.content - content_lower = content.lower() + content = message.content.strip() + lower = content.lower() user = message.author.name guild = message.guild.name if message.guild else "Direct Message" guild_id = message.guild.id @@ -49,26 +47,26 @@ async def on_message(message): if message.author == garfbot.user: return - if content_lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): - question = content[12:] if content_lower.startswith("hey garfield") else message.content + 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}") await message.channel.send(answer) - if content_lower.startswith('garfpic '): + 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) # Wikipedia - if content_lower.startswith('garfwiki '): + if lower.startswith('garfwiki '): search_term = message.content[9:] summary = await wikisum(search_term) await message.channel.send(summary) # QR codes - if content_lower.startswith('garfqr '): + if lower.startswith('garfqr '): text = message.content[7:] if len(text) > 1000: await message.channel.send("❌ Text too long! Maximum 1000 characters.") @@ -85,7 +83,7 @@ async def on_message(message): query = message.content.split() target = query[-1] - if content_lower.startswith("garfping "): + if lower.startswith("garfping "): try: logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}") if is_private(target): @@ -97,7 +95,7 @@ async def on_message(message): except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") - if content_lower.startswith("garfdns "): + if lower.startswith("garfdns "): try: logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}") if is_private(target): @@ -109,7 +107,7 @@ async def on_message(message): except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") - if content_lower.startswith("garfhack "): + if lower.startswith("garfhack "): try: logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}") if is_private(target): @@ -123,7 +121,7 @@ async def on_message(message): await message.channel.send(f"`GarfBot Error: {str(e)}`") # Kroger Shopping - if content_lower.startswith("garfshop "): + if lower.startswith("garfshop "): try: kroken = kroger_token() kroger_query = message.content.split() @@ -152,12 +150,12 @@ async def on_message(message): if message.guild: responses = garf_respond.get_responses(guild_id) - if content_lower.startswith('garfbot response '): + if lower.startswith('garfbot response '): await garf_respond.garfbot_response(message, content) return for trigger, response in responses.items(): - if trigger.lower() in content_lower: + if trigger.lower() in lower: await message.channel.send(response) break diff --git a/garfpy/respond.py b/garfpy/respond.py index 89ebe37..5c26b5b 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -44,33 +44,27 @@ class GarfbotRespond: logger.info(message.content) - add_pattern = r'garfbot\s+response\s+add\s+["\']([^"\']+)["\']\s+["\']([^"\']+)["\']' - add_match = re.search(add_pattern, content, re.IGNORECASE) - - if add_match: - trigger = add_match.group(1) - response_text = add_match.group(2) + match = re.search(r'garfbot response add "(.+?)" "(.+?)"', content, re.IGNORECASE) + if match: + trigger = match.group(1) + response_text = match.group(2) await self.add_response(message, guild_id, trigger, response_text) return - add_simple_pattern = r'garfbot\s+response\s+add\s+(\S+)\s+(\S+)' - add_simple_match = re.search(add_simple_pattern, content, re.IGNORECASE) - - if add_simple_match: - trigger = add_simple_match.group(1) - response_text = add_simple_match.group(2) + match = re.search(r'garfbot response add (\S+) (.+)', content, re.IGNORECASE) + if match: + trigger = match.group(1) + response_text = match.group(2) await self.add_response(message, guild_id, trigger, response_text) return - remove_pattern = r'garfbot\s+response\s+remove\s+(\S+)' - remove_match = re.search(remove_pattern, content, re.IGNORECASE) - - if remove_match: - trigger = remove_match.group(1).strip() + match = re.search(r'garfbot\s+response\s+remove\s+(\S+)', content, re.IGNORECASE) + if match: + trigger = match.group(1).strip() await self.remove_response(message, guild_id, trigger) return - if re.search(r'garfbot\s+response\s+list', content, re.IGNORECASE): + if content.lower() == "garfbot response list": await self.list_responses(message, guild_id) return From 39b821ff7fa17c10c27b5cbe927ee980a502973e Mon Sep 17 00:00:00 2001 From: crate Date: Wed, 4 Jun 2025 03:14:25 -0500 Subject: [PATCH 08/21] fix regex hopefully --- garfpy/respond.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/garfpy/respond.py b/garfpy/respond.py index 5c26b5b..4a28766 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -44,7 +44,7 @@ class GarfbotRespond: logger.info(message.content) - match = re.search(r'garfbot response add "(.+?)" "(.+?)"', content, re.IGNORECASE) + match = re.search(r'garfbot response add "(.+)" "(.+)"', content, re.IGNORECASE) if match: trigger = match.group(1) response_text = match.group(2) @@ -58,7 +58,7 @@ class GarfbotRespond: await self.add_response(message, guild_id, trigger, response_text) return - match = re.search(r'garfbot\s+response\s+remove\s+(\S+)', content, re.IGNORECASE) + match = re.search(r'garfbot\s+response\s+remove\s+(.+)', content, re.IGNORECASE) if match: trigger = match.group(1).strip() await self.remove_response(message, guild_id, trigger) From 82561f050f72375a1201d85d4204d4c640505b0f Mon Sep 17 00:00:00 2001 From: crate Date: Wed, 4 Jun 2025 21:36:43 -0500 Subject: [PATCH 09/21] getting classy --- garfmain.py | 17 ++--- garfpy/__init__.py | 1 + garfpy/garfai.py | 152 +++++++++++++++++++++++---------------------- garfpy/respond.py | 32 +++++----- garfpy/wiki.py | 4 +- 5 files changed, 105 insertions(+), 101 deletions(-) diff --git a/garfmain.py b/garfmain.py index bf0e754..eb05366 100644 --- a/garfmain.py +++ b/garfmain.py @@ -6,8 +6,7 @@ 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, wikisum, generate_qr, GarfAI, GarfbotRespond) gapikey = config.GIF_TOKEN @@ -21,14 +20,16 @@ intents.messages = True intents.message_content = True garfbot = discord.Client(intents=intents) + garf_respond = GarfbotRespond() +garfield = GarfAI() @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) @@ -49,7 +50,7 @@ async def on_message(message): 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) + answer = await garfield.generate_chat(question) logger.info(f"Chat Request - User: {user}, Server: {guild}, Prompt: {question}") await message.channel.send(answer) @@ -57,7 +58,7 @@ async def on_message(message): 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 '): @@ -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}...`") diff --git a/garfpy/__init__.py b/garfpy/__init__.py index 353e5c8..9358eee 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -13,4 +13,5 @@ from .iputils import is_private from .aod import aod_message from .wiki import wikisum from .qr import generate_qr +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..b9b1e42 100644 --- a/garfpy/garfai.py +++ b/garfpy/garfai.py @@ -7,83 +7,85 @@ import discord 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) - -# 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`" + # GarfChats + @staticmethod + 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`" 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 index 0904bc4..d0bd570 100644 --- a/garfpy/wiki.py +++ b/garfpy/wiki.py @@ -1,10 +1,10 @@ import wikipedia -from garfpy import generate_chat +from garfpy import GarfAI async def wikisum(search_term): try: summary = wikipedia.summary(search_term) - garfsum = await generate_chat(f"Please summarize in your own words: {summary}") + garfsum = await GarfAI.generate_chat(f"Please summarize in your own words: {summary}") return garfsum From 353284b2c3869aa1296ec60c436bd79df1f38af1 Mon Sep 17 00:00:00 2001 From: crate Date: Thu, 5 Jun 2025 13:33:14 -0500 Subject: [PATCH 10/21] looks good --- garfmain.py | 33 +++++----------- garfpy/__init__.py | 11 +----- garfpy/garfai.py | 11 +++++- garfpy/kroger.py | 94 ++++++++++++++++++++++++++++------------------ garfpy/wiki.py | 12 ------ 5 files changed, 79 insertions(+), 82 deletions(-) delete mode 100644 garfpy/wiki.py diff --git a/garfmain.py b/garfmain.py index eb05366..4b900a0 100644 --- a/garfmain.py +++ b/garfmain.py @@ -5,8 +5,8 @@ import subprocess from garfpy import( logger, is_private, - kroger_token, find_store, search_product, - aod_message, wikisum, generate_qr, GarfAI, GarfbotRespond) + aod_message, generate_qr, + Kroger, GarfAI, GarfbotRespond) gapikey = config.GIF_TOKEN @@ -20,9 +20,9 @@ intents.messages = True intents.message_content = True garfbot = discord.Client(intents=intents) - garf_respond = GarfbotRespond() garfield = GarfAI() +kroger = Kroger() @garfbot.event @@ -49,9 +49,9 @@ 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 garfield.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 '): @@ -62,8 +62,8 @@ async def on_message(message): # 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 @@ -124,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 9358eee..ce62920 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -1,17 +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 b9b1e42..d9f3459 100644 --- a/garfpy/garfai.py +++ b/garfpy/garfai.py @@ -4,6 +4,7 @@ import config import aiohttp import asyncio import discord +import wikipedia from openai import AsyncOpenAI from garfpy import logger @@ -66,8 +67,6 @@ class GarfAI: self.image_request_queue.task_done() await asyncio.sleep(2) - # GarfChats - @staticmethod async def generate_chat(self, question): try: client = AsyncOpenAI(api_key = self.openaikey) @@ -89,3 +88,11 @@ class GarfAI: 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/wiki.py b/garfpy/wiki.py deleted file mode 100644 index d0bd570..0000000 --- a/garfpy/wiki.py +++ /dev/null @@ -1,12 +0,0 @@ -import wikipedia -from garfpy import GarfAI - -async def wikisum(search_term): - try: - summary = wikipedia.summary(search_term) - garfsum = await GarfAI.generate_chat(f"Please summarize in your own words: {summary}") - - return garfsum - - except Exception as e: - return e \ No newline at end of file From 2415a8146da517fc9eac506c9887036696169847 Mon Sep 17 00:00:00 2001 From: crate Date: Thu, 5 Jun 2025 16:25:45 -0500 Subject: [PATCH 11/21] looks good --- garfmain.py | 88 ++++++++++++++-------------------------------- garfpy/__init__.py | 9 ++--- garfpy/iputils.py | 79 ++++++++++++++++++++++++++++++++--------- 3 files changed, 93 insertions(+), 83 deletions(-) diff --git a/garfmain.py b/garfmain.py index 4b900a0..e45462a 100644 --- a/garfmain.py +++ b/garfmain.py @@ -1,10 +1,9 @@ import config import asyncio import discord -import subprocess from garfpy import( - logger, is_private, + logger, IPUtils, aod_message, generate_qr, Kroger, GarfAI, GarfbotRespond) @@ -22,9 +21,11 @@ garfbot = discord.Client(intents=intents) garf_respond = GarfbotRespond() garfield = GarfAI() +iputils = IPUtils() kroger = Kroger() + @garfbot.event async def on_ready(): try: @@ -37,28 +38,18 @@ async def on_ready(): @garfbot.event async def on_message(message): - - content = message.content.strip() - lower = content.lower() - user = message.author.name - guild = message.guild.name if message.guild else "Direct Message" - guild_id = message.guild.id - - # Chats & pics if message.author == garfbot.user: return + + content = message.content.strip() + lower = content.lower() + user_name = message.author.name + guild_id = message.guild.id + guild_name = message.guild.name if message.guild else "Direct Message" - if lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): - 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 garfield.garfpic(message, prompt) + # IP utils + if message.guild and lower.startswith(("garfping ", "garfdns ", "garfhack ")): + await iputils.scan(message, user_name, guild_name, lower) # Wikipedia if lower.startswith('garfwiki '): @@ -80,47 +71,6 @@ async def on_message(message): logger.error(e) await message.channel.send(e) - # IP utils - query = message.content.split() - target = query[-1] - - if lower.startswith("garfping "): - try: - logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}") - if is_private(target): - 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) - await message.channel.send(f"`Ping result for {target}:`\n```\n{result.stdout}\n```") - except Exception as e: - await message.channel.send(f"`GarfBot Error: {str(e)}`") - - if lower.startswith("garfdns "): - try: - logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}") - if is_private(target): - 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) - await message.channel.send(f"`NSLookup result for {target}:`\n```\n{result.stdout}\n```") - except Exception as e: - await message.channel.send(f"`GarfBot Error: {str(e)}`") - - if lower.startswith("garfhack "): - try: - logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}") - if is_private(target): - 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}...`") - result = subprocess.run(['nmap', '-Pn', '-O', '-v', target], capture_output=True, text=True) - await message.channel.send(f"`Ping result for {target}:`\n```\n{result.stdout}\n```") - except Exception as e: - await message.channel.send(f"`GarfBot Error: {str(e)}`") - # Kroger Shopping if lower.startswith("garfshop "): try: @@ -147,6 +97,20 @@ async def on_message(message): await message.channel.send(response) break + # Chats & pics + elif lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): + prompt = content[12:] if lower.startswith("hey garfield") else message.content + answer = await garfield.generate_chat(prompt) + logger.info(f"Chat Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}") + await message.channel.send(answer) + + elif lower.startswith('garfpic '): + prompt = content[8:] + logger.info(f"Image Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}") + await message.channel.send(f"`Please wait... image generation queued: {prompt}`") + await garfield.garfpic(message, prompt) + + # Run Garfbot async def garfbot_connect(): while True: diff --git a/garfpy/__init__.py b/garfpy/__init__.py index ce62920..0234976 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -2,9 +2,10 @@ from .log import logger from .kroger import Kroger -from .iputils import is_private -from .aod import aod_message -from .qr import generate_qr +# from .iputils import scan from .kroger import Kroger from .garfai import GarfAI -from .respond import GarfbotRespond \ No newline at end of file +from .respond import GarfbotRespond +from .aod import aod_message +from .qr import generate_qr +from .iputils import IPUtils \ No newline at end of file diff --git a/garfpy/iputils.py b/garfpy/iputils.py index 4f7d1d5..551734d 100644 --- a/garfpy/iputils.py +++ b/garfpy/iputils.py @@ -1,19 +1,64 @@ +import discord import ipaddress +import subprocess +from garfpy import logger -def is_private(target): - try: - ip_obj = ipaddress.ip_address(target) - if ip_obj.is_private: - return True - except ValueError: - if "crate.lan" in target.lower(): - return True - if "crate.zip" in target.lower(): - return True - if "memtec.org" in target.lower(): - return True - if "crateit.net" in target.lower(): - return True - if "garfbot.art" in target.lower(): - return True - return False + +class IPUtils: + def is_private(self, target): + try: + ip_obj = ipaddress.ip_address(target) + if ip_obj.is_private: + return True + except ValueError: + if "crate.lan" in target.lower(): + return True + if "crate.zip" in target.lower(): + return True + if "memtec.org" in target.lower(): + return True + if "crateit.net" in target.lower(): + return True + if "garfbot.art" in target.lower(): + return True + return False + + async def scan(self, message, user, guild, query): + split = query.split() + target = split[-1] + if self.is_private(target): + return + + if query.startswith("garfping "): + try: + logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}") + await message.channel.send(f"`Pinging {target}...`") + result = subprocess.run(['ping', '-c', '4', target], capture_output=True, text=True) + embed = discord.Embed(title=f"Ping result:", color=0x4d4d4d) + embed.add_field(name=target, value=f"```{result.stdout}```") + await message.channel.send(embed=embed) + except Exception as e: + await message.channel.send(f"`GarfBot Error: {str(e)}`") + + if query.startswith("garfdns "): + try: + logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}") + await message.channel.send(f"`Requesting {target}...`") + result = subprocess.run(['nslookup', target], capture_output=True, text=True) + embed = discord.Embed(title=f"NSLookup result:", color=0x4d4d4d) + embed.add_field(name=target, value=f"```{result.stdout}```") + await message.channel.send(embed=embed) + except Exception as e: + await message.channel.send(f"`GarfBot Error: {str(e)}`") + + if query.startswith("garfhack "): + try: + logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}") + await message.channel.send(f"`Scanning {target}...`") + result = subprocess.run(['nmap', '-Pn', '-O', '-v', target], capture_output=True, text=True) + embed = discord.Embed(title=f"Nmap scan result:", color=0x4d4d4d) + embed.add_field(name=target, value=f"```{result.stdout}```") + embed.set_footer(text="https://nmap.org/") + await message.channel.send(embed=embed) + except Exception as e: + await message.channel.send(f"`GarfBot Error: {str(e)}`") \ No newline at end of file From 6b9c2b638ab615899a925fefb4e6afaf77dd7126 Mon Sep 17 00:00:00 2001 From: crate Date: Thu, 5 Jun 2025 17:25:43 -0500 Subject: [PATCH 12/21] format etc --- garfmain.py | 75 ++++++++++++++--------- garfpy/__init__.py | 3 +- garfpy/aod.py | 149 +++++++++++++++++++++++++++------------------ garfpy/garfai.py | 38 ++++++------ garfpy/iputils.py | 36 +++++++---- garfpy/kroger.py | 55 ++++++++++------- garfpy/log.py | 17 +++--- garfpy/qr.py | 17 +++--- garfpy/respond.py | 110 +++++++++++++++++---------------- 9 files changed, 284 insertions(+), 216 deletions(-) diff --git a/garfmain.py b/garfmain.py index e45462a..ac6224c 100644 --- a/garfmain.py +++ b/garfmain.py @@ -2,10 +2,15 @@ import config import asyncio import discord -from garfpy import( - logger, IPUtils, - aod_message, generate_qr, - Kroger, GarfAI, GarfbotRespond) +from garfpy import ( + logger, + IPUtils, + aod_message, + generate_qr, + Kroger, + GarfAI, + GarfbotRespond, +) gapikey = config.GIF_TOKEN @@ -25,13 +30,14 @@ iputils = IPUtils() kroger = Kroger() - @garfbot.event async def on_ready(): try: 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: logger.error(e) @@ -40,7 +46,7 @@ async def on_ready(): async def on_message(message): if message.author == garfbot.user: return - + content = message.content.strip() lower = content.lower() user_name = message.author.name @@ -52,13 +58,13 @@ async def on_message(message): await iputils.scan(message, user_name, guild_name, lower) # Wikipedia - if lower.startswith('garfwiki '): + if lower.startswith("garfwiki "): query = message.content[9:] summary = await garfield.wikisum(query) await message.channel.send(summary) # QR codes - if lower.startswith('garfqr '): + if lower.startswith("garfqr "): text = message.content[7:] if len(text) > 1000: await message.channel.send("❌ Text too long! Maximum 1000 characters.") @@ -80,36 +86,44 @@ async def on_message(message): except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") + # Chats & pics + elif lower.startswith("hey garfield") or isinstance( + message.channel, discord.DMChannel + ): + prompt = content[12:] if lower.startswith("hey garfield") else message.content + answer = await garfield.generate_chat(prompt) + logger.info( + f"Chat Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}" + ) + await message.channel.send(answer) + + elif lower.startswith("garfpic "): + prompt = content[8:] + logger.info( + f"Image Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}" + ) + await message.channel.send( + f"`Please wait... image generation queued: {prompt}`" + ) + await garfield.garfpic(message, prompt) + # Army of Dawn Server only!! - if message.guild and message.guild.id == 719605634772893757: + elif message.guild and message.guild.id == 719605634772893757: await aod_message(garfbot, message) # Auto-responses - if message.guild: + elif message.guild: responses = garf_respond.get_responses(guild_id) - - if lower.startswith('garfbot response '): + + if lower.startswith("garfbot response "): await garf_respond.garfbot_response(message, content) return - + for trigger, response in responses.items(): if trigger.lower() in lower: await message.channel.send(response) break - # Chats & pics - elif lower.startswith("hey garfield") or isinstance(message.channel, discord.DMChannel): - prompt = content[12:] if lower.startswith("hey garfield") else message.content - answer = await garfield.generate_chat(prompt) - logger.info(f"Chat Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}") - await message.channel.send(answer) - - elif lower.startswith('garfpic '): - prompt = content[8:] - logger.info(f"Image Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}") - await message.channel.send(f"`Please wait... image generation queued: {prompt}`") - await garfield.garfpic(message, prompt) - # Run Garfbot async def garfbot_connect(): @@ -117,9 +131,10 @@ async def garfbot_connect(): try: await garfbot.start(garfkey) except Exception as e: - e = str(e) - logger.error(f"Garfbot couldn't connect! {e}") - await asyncio.sleep(60) + e = str(e) + logger.error(f"Garfbot couldn't connect! {e}") + await asyncio.sleep(60) + if __name__ == "__main__": asyncio.run(garfbot_connect()) diff --git a/garfpy/__init__.py b/garfpy/__init__.py index 0234976..a11fd0b 100644 --- a/garfpy/__init__.py +++ b/garfpy/__init__.py @@ -2,10 +2,9 @@ from .log import logger from .kroger import Kroger -# from .iputils import scan from .kroger import Kroger from .garfai import GarfAI from .respond import GarfbotRespond from .aod import aod_message from .qr import generate_qr -from .iputils import IPUtils \ No newline at end of file +from .iputils import IPUtils diff --git a/garfpy/aod.py b/garfpy/aod.py index 0013d55..e13b651 100644 --- a/garfpy/aod.py +++ b/garfpy/aod.py @@ -11,6 +11,7 @@ from collections import defaultdict meows_file = "meow_counts.json" stats_file = "user_stats.json" + def json_load(file_path, default): if os.path.isfile(file_path): with open(file_path, "r") as f: @@ -18,77 +19,105 @@ def json_load(file_path, default): else: return default + meow_counts = defaultdict(int, json_load(meows_file, {})) user_stats = json_load(stats_file, {}) + async def aod_message(garfbot, message): - if "meow" in message.content.lower(): - logger.info(f"Meow detected! {message.author.name} said: {message.content}") + if "meow" in message.content.lower(): + logger.info(f"Meow detected! {message.author.name} said: {message.content}") - meow_counts[str(message.author.id)] += 1 + meow_counts[str(message.author.id)] += 1 - with open(meows_file, "w") as f: - json.dump(dict(meow_counts), f) + with open(meows_file, "w") as f: + json.dump(dict(meow_counts), f) - if message.content.lower() == "meowcount": - response = f"My records show that <@{message.author.id}> has meowed {meow_counts[str(message.author.id)]} time(s). Have a nice day." - await message.channel.send(response) + if message.content.lower() == "meowcount": + response = f"My records show that <@{message.author.id}> has meowed {meow_counts[str(message.author.id)]} time(s). Have a nice day." + await message.channel.send(response) - if message.content.lower() == "top meowers": - top_meowers = sorted(meow_counts.items(), key=itemgetter(1), reverse=True)[:10] - embed = discord.Embed(title="Top Meowers :cat:", color=0x000000) - for i, (user_id, meow_count) in enumerate(top_meowers): - user = await garfbot.fetch_user(int(user_id)) - embed.add_field(name=f"{i+1}. {user.name}", value=f"{meow_count} meows", inline=False) - await message.channel.send(embed=embed) + if message.content.lower() == "top meowers": + top_meowers = sorted(meow_counts.items(), key=itemgetter(1), reverse=True)[ + :10 + ] + embed = discord.Embed(title="Top Meowers :cat:", color=0x000000) + for i, (user_id, meow_count) in enumerate(top_meowers): + user = await garfbot.fetch_user(int(user_id)) + embed.add_field( + name=f"{i + 1}. {user.name}", + value=f"{meow_count} meows", + inline=False, + ) + await message.channel.send(embed=embed) - if message.content.lower() == "checking in": - user_id = str(message.author.id) - if user_id in user_stats and user_stats[user_id]["check_in_time"] is not None: - await message.channel.send(f"{message.author.mention} You are already checked in. Please check out first.") - return + if message.content.lower() == "checking in": + user_id = str(message.author.id) + if user_id in user_stats and user_stats[user_id]["check_in_time"] is not None: + await message.channel.send( + f"{message.author.mention} You are already checked in. Please check out first." + ) + return - check_in_time = datetime.now().timestamp() - if user_id not in user_stats: - user_stats[user_id] = {"check_ins": 0, "total_time": 0, "check_in_time": None} - user_stats[user_id]["check_in_time"] = check_in_time - await message.channel.send(f"{message.author.mention} You have been checked in. Please mute your microphone.") + check_in_time = datetime.now().timestamp() + if user_id not in user_stats: + user_stats[user_id] = { + "check_ins": 0, + "total_time": 0, + "check_in_time": None, + } + user_stats[user_id]["check_in_time"] = check_in_time + await message.channel.send( + f"{message.author.mention} You have been checked in. Please mute your microphone." + ) - elif message.content.lower() == "checking out": - user_id = str(message.author.id) - if user_id not in user_stats or user_stats[user_id]["check_in_time"] is None: - await message.channel.send(f"{message.author.mention} You have not checked in yet. Please check in first.") - return + elif message.content.lower() == "checking out": + user_id = str(message.author.id) + if user_id not in user_stats or user_stats[user_id]["check_in_time"] is None: + await message.channel.send( + f"{message.author.mention} You have not checked in yet. Please check in first." + ) + return - check_out_time = datetime.now().timestamp() - check_in_time = user_stats[user_id]["check_in_time"] - time_delta = check_out_time - check_in_time - user_stats[user_id]["check_ins"] += 1 - user_stats[user_id]["total_time"] += time_delta - user_stats[user_id]["check_in_time"] = None + check_out_time = datetime.now().timestamp() + check_in_time = user_stats[user_id]["check_in_time"] + time_delta = check_out_time - check_in_time + user_stats[user_id]["check_ins"] += 1 + user_stats[user_id]["total_time"] += time_delta + user_stats[user_id]["check_in_time"] = None - with open("user_stats.json", "w") as f: - json.dump(user_stats, f) - await message.channel.send(f"{message.author.mention} You have been checked out. Your session was {time_delta:.2f} seconds.") + with open("user_stats.json", "w") as f: + json.dump(user_stats, f) + await message.channel.send( + f"{message.author.mention} You have been checked out. Your session was {time_delta:.2f} seconds." + ) - elif message.content.lower() == "stats": - stats_embed = discord.Embed(title="User stats :trophy:", color=0x000000) - sorted_user_stats = sorted(user_stats.items(), key=lambda x: x[1]["total_time"], reverse=True) - table_rows = [["Name", "Check-ins", "Total Time"]] - for user_id, stats in sorted_user_stats: - if stats["check_in_time"] is None: - total_time_seconds = stats["total_time"] - hours, total_time_seconds = divmod(total_time_seconds, 3600) - minutes, total_time_seconds = divmod(total_time_seconds, 60) - seconds, fractions = divmod(total_time_seconds, 1) - fractions_str = f"{fractions:.3f}"[2:] - username = garfbot.get_user(int(user_id)).name - table_rows.append([username, str(stats["check_ins"]), f"{int(hours)}h {int(minutes)}m {int(seconds)}s {fractions_str}ms"]) - else: - username = garfbot.get_user(int(user_id)).name - table_rows.append([username, "Currently checked in", "-"]) - table_columns = list(zip(*table_rows[1:])) - table_fields = table_rows[0] - for field, values in zip(table_fields, table_columns): - stats_embed.add_field(name=field, value="\n".join(values), inline=True) - await message.channel.send(embed=stats_embed) + elif message.content.lower() == "stats": + stats_embed = discord.Embed(title="User stats :trophy:", color=0x000000) + sorted_user_stats = sorted( + user_stats.items(), key=lambda x: x[1]["total_time"], reverse=True + ) + table_rows = [["Name", "Check-ins", "Total Time"]] + for user_id, stats in sorted_user_stats: + if stats["check_in_time"] is None: + total_time_seconds = stats["total_time"] + hours, total_time_seconds = divmod(total_time_seconds, 3600) + minutes, total_time_seconds = divmod(total_time_seconds, 60) + seconds, fractions = divmod(total_time_seconds, 1) + fractions_str = f"{fractions:.3f}"[2:] + username = garfbot.get_user(int(user_id)).name + table_rows.append( + [ + username, + str(stats["check_ins"]), + f"{int(hours)}h {int(minutes)}m {int(seconds)}s {fractions_str}ms", + ] + ) + else: + username = garfbot.get_user(int(user_id)).name + table_rows.append([username, "Currently checked in", "-"]) + table_columns = list(zip(*table_rows[1:])) + table_fields = table_rows[0] + for field, values in zip(table_fields, table_columns): + stats_embed.add_field(name=field, value="\n".join(values), inline=True) + await message.channel.send(embed=stats_embed) diff --git a/garfpy/garfai.py b/garfpy/garfai.py index d9f3459..2e3c883 100644 --- a/garfpy/garfai.py +++ b/garfpy/garfai.py @@ -17,16 +17,13 @@ class GarfAI: self.image_request_queue = asyncio.Queue() async def garfpic(self, message, prompt): - await self.image_request_queue.put({'message': message, 'prompt': prompt}) + await self.image_request_queue.put({"message": message, "prompt": prompt}) async def generate_image(self, prompt): try: - client = AsyncOpenAI(api_key = self.openaikey) + client = AsyncOpenAI(api_key=self.openaikey) response = await client.images.generate( - model=self.imgmodel, - prompt=prompt, - n=1, - size="1024x1024" + model=self.imgmodel, prompt=prompt, n=1, size="1024x1024" ) image_url = response.data[0].url return image_url @@ -37,14 +34,14 @@ class GarfAI: return f"`GarfBot Error: ({e.status_code}) - Monday`" except Exception as e: logger.error(e) - return f"`GarfBot Error: Lasagna`" + return "`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'] + 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...") @@ -53,7 +50,7 @@ class GarfAI: 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') + 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: @@ -69,14 +66,17 @@ class GarfAI: async def generate_chat(self, question): try: - client = AsyncOpenAI(api_key = self.openaikey) + 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}"} + { + "role": "system", + "content": "Pretend you are sarcastic Garfield.", + }, + {"role": "user", "content": f"{question}"}, ], - max_tokens=400 + max_tokens=400, ) answer = response.choices[0].message.content return answer.replace("an AI language model", "a cartoon animal") @@ -84,15 +84,17 @@ class GarfAI: return f"`GarfBot Error: {e}`" except openai.APIError as e: logger.info(e, flush=True) - return f"`GarfBot Error: Monday`" + return "`GarfBot Error: Monday`" except Exception as e: logger.info(e, flush=True) - return f"`GarfBot Error: Lasagna`" + return "`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}") + 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 + return e diff --git a/garfpy/iputils.py b/garfpy/iputils.py index 551734d..1f69723 100644 --- a/garfpy/iputils.py +++ b/garfpy/iputils.py @@ -4,7 +4,7 @@ import subprocess from garfpy import logger -class IPUtils: +class IPUtils: def is_private(self, target): try: ip_obj = ipaddress.ip_address(target) @@ -31,10 +31,14 @@ class IPUtils: if query.startswith("garfping "): try: - logger.info(f"Ping Request - User: {user}, Server: {guild}, Target: {target}") + logger.info( + f"Ping Request - User: {user}, Server: {guild}, Target: {target}" + ) await message.channel.send(f"`Pinging {target}...`") - result = subprocess.run(['ping', '-c', '4', target], capture_output=True, text=True) - embed = discord.Embed(title=f"Ping result:", color=0x4d4d4d) + result = subprocess.run( + ["ping", "-c", "4", target], capture_output=True, text=True + ) + embed = discord.Embed(title=f"Ping result:", color=0x4D4D4D) embed.add_field(name=target, value=f"```{result.stdout}```") await message.channel.send(embed=embed) except Exception as e: @@ -42,10 +46,14 @@ class IPUtils: if query.startswith("garfdns "): try: - logger.info(f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}") + logger.info( + f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}" + ) await message.channel.send(f"`Requesting {target}...`") - result = subprocess.run(['nslookup', target], capture_output=True, text=True) - embed = discord.Embed(title=f"NSLookup result:", color=0x4d4d4d) + result = subprocess.run( + ["nslookup", target], capture_output=True, text=True + ) + embed = discord.Embed(title=f"NSLookup result:", color=0x4D4D4D) embed.add_field(name=target, value=f"```{result.stdout}```") await message.channel.send(embed=embed) except Exception as e: @@ -53,12 +61,16 @@ class IPUtils: if query.startswith("garfhack "): try: - logger.info(f"Nmap Request - User: {user}, Server: {guild}, Target: {target}") - await message.channel.send(f"`Scanning {target}...`") - result = subprocess.run(['nmap', '-Pn', '-O', '-v', target], capture_output=True, text=True) - embed = discord.Embed(title=f"Nmap scan result:", color=0x4d4d4d) + logger.info( + f"Nmap Request - User: {user}, Server: {guild}, Target: {target}" + ) + await message.channel.send(f"`Scanning {target}...`") + result = subprocess.run( + ["nmap", "-Pn", "-O", "-v", target], capture_output=True, text=True + ) + embed = discord.Embed(title=f"Nmap scan result:", color=0x4D4D4D) embed.add_field(name=target, value=f"```{result.stdout}```") embed.set_footer(text="https://nmap.org/") await message.channel.send(embed=embed) except Exception as e: - await message.channel.send(f"`GarfBot Error: {str(e)}`") \ No newline at end of file + await message.channel.send(f"`GarfBot Error: {str(e)}`") diff --git a/garfpy/kroger.py b/garfpy/kroger.py index b126033..66b7d8f 100644 --- a/garfpy/kroger.py +++ b/garfpy/kroger.py @@ -8,44 +8,51 @@ 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() + self.auth = b64encode( + f"{self.client_id}:{self.client_secret}".encode() + ).decode() def kroger_token(self): headers = { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': f'Basic {self.auth}' + "Content-Type": "application/x-www-form-urlencoded", + "Authorization": f"Basic {self.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'] + return response.json()["access_token"] def find_store(self, zipcode, kroken): headers = { - 'Authorization': f'Bearer {kroken}', + "Authorization": f"Bearer {kroken}", } params = { - 'filter.zipCode.near': zipcode, - 'filter.limit': 1, + "filter.zipCode.near": zipcode, + "filter.limit": 1, } - response = requests.get('https://api.kroger.com/v1/locations', headers=headers, params=params) + 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}', + "Authorization": f"Bearer {kroken}", } params = { - 'filter.term': product, - 'filter.locationId': loc_id, - 'filter.limit': 10 + "filter.term": product, + "filter.locationId": loc_id, + "filter.limit": 10, } - response = requests.get('https://api.kroger.com/v1/products', headers=headers, params=params) + response = requests.get( + "https://api.kroger.com/v1/products", headers=headers, params=params + ) return response.json() def garfshop(self, query): @@ -55,15 +62,17 @@ class Kroger: 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'] + 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']) + 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'] + product_name = item["description"] + price = item["items"][0]["price"]["regular"] response += f"- `${price}`: {product_name} \n" return response except Exception as e: diff --git a/garfpy/log.py b/garfpy/log.py index 3c17d94..de01b30 100644 --- a/garfpy/log.py +++ b/garfpy/log.py @@ -1,19 +1,18 @@ import logging from logging.handlers import TimedRotatingFileHandler -logger = logging.getLogger('garflog') +logger = logging.getLogger("garflog") logger.setLevel(logging.INFO) -formatter=logging.Formatter( - '%(asctime)s [%(levelname)s] %(message)s', - datefmt='%Y-%m-%d %H:%M:%S' - ) +formatter = logging.Formatter( + "%(asctime)s [%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S" +) file_handler = TimedRotatingFileHandler( - 'garfbot.log', - when='midnight', + "garfbot.log", + when="midnight", interval=1, backupCount=7, - delay=True # Counter-intuitively, this will flush output immediately - ) + delay=True, # Counter-intuitively, this will flush output immediately +) file_handler.setFormatter(formatter) console_handler = logging.StreamHandler() console_handler.setFormatter(formatter) diff --git a/garfpy/qr.py b/garfpy/qr.py index ceb3877..0ac4b48 100644 --- a/garfpy/qr.py +++ b/garfpy/qr.py @@ -4,7 +4,7 @@ from io import BytesIO def calculate_qr_settings(text): text_length = len(text) - + if text_length <= 25: version = 1 box_size = 12 @@ -38,26 +38,27 @@ def calculate_qr_settings(text): else: version = None box_size = 3 - + return version, box_size + async def generate_qr(text): version, box_size = calculate_qr_settings(text) - + qr = qrcode.QRCode( version=version, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=box_size, border=4, ) - + qr.add_data(text) qr.make(fit=True) - + qr_image = qr.make_image(fill_color="black", back_color="white") - + img_buffer = BytesIO() - qr_image.save(img_buffer, format='PNG') + qr_image.save(img_buffer, format="PNG") img_buffer.seek(0) - return img_buffer \ No newline at end of file + return img_buffer diff --git a/garfpy/respond.py b/garfpy/respond.py index a7941e0..ab3e55d 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -8,16 +8,22 @@ import re class GarfbotRespond: def __init__(self): self.guild_responses = {} - self.responses_file = 'responses.json' + 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: + with open(self.responses_file, "r", encoding="utf-8") as f: 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)") + 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.guild_responses = {} @@ -27,10 +33,14 @@ class GarfbotRespond: def save_responses(self): try: 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) - 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)") + 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}") @@ -43,121 +53,113 @@ class GarfbotRespond: guild_id = message.guild.id logger.info(message.content) - + match = re.search(r'garfbot response add "(.+)" "(.+)"', content, re.IGNORECASE) if match: trigger = match.group(1) response_text = match.group(2) await self.add_response(message, guild_id, trigger, response_text) return - - match = re.search(r'garfbot response add (\S+) (.+)', content, re.IGNORECASE) + + match = re.search(r"garfbot response add (\S+) (.+)", content, re.IGNORECASE) if match: - trigger = match.group(1) + trigger = match.group(1) response_text = match.group(2) await self.add_response(message, guild_id, trigger, response_text) return - - match = re.search(r'garfbot\s+response\s+remove\s+(.+)', content, re.IGNORECASE) + + match = re.search(r"garfbot\s+response\s+remove\s+(.+)", content, re.IGNORECASE) if match: trigger = match.group(1).strip() await self.remove_response(message, guild_id, trigger) return - + if content.lower() == "garfbot response list": await self.list_responses(message, guild_id) return - + await message.channel.send( "**Garfbot Auto-Response Commands:**\n" - "`garfbot response add \"trigger\" \"response\"`\n" - "`garfbot response remove \"trigger\"`\n" + '`garfbot response add "trigger" "response"`\n' + '`garfbot response remove "trigger"`\n' "`garfbot response list`\n\n" "**Examples:**\n" - "`garfbot response add \"hi\" \"Hello there!\"`\n" - "`garfbot response add \"that's what\" \"That's what she said!\"`\n" - "`garfbot response remove \"hi\"`" + '`garfbot response add "hi" "Hello there!"`\n' + '`garfbot response add "that\'s what" "That\'s what she said!"`\n' + '`garfbot response remove "hi"`' ) async def add_response(self, message, guild_id, trigger, response_text): if not response_text or not trigger: await message.channel.send("❌ Trigger and response must not be null.") return - + responses = self.get_responses(guild_id) responses[trigger] = response_text self.guild_responses[guild_id] = responses self.save_responses() - - embed = discord.Embed( - title="✅ Auto-response Added.", - color=0x00ff00 - ) + + embed = discord.Embed(title="✅ Auto-response Added.", color=0x00FF00) embed.add_field(name="Trigger", value=f"`{trigger}`", inline=True) embed.add_field(name="Response", value=f"`{response_text}`", inline=True) embed.set_footer(text=f"Server: {message.guild.name}") - + await message.channel.send(embed=embed) async def remove_response(self, message, guild_id, trigger): responses = self.get_responses(guild_id) - + if trigger in responses: removed_response = responses[trigger] del responses[trigger] self.guild_responses[guild_id] = responses self.save_responses() - - embed = discord.Embed( - title="✅ Auto-response Removed.", - color=0xff6b6b - ) + + embed = discord.Embed(title="✅ Auto-response Removed.", color=0xFF6B6B) embed.add_field(name="Trigger", value=f"`{trigger}`", inline=True) embed.add_field(name="Response", value=f"`{removed_response}`", inline=True) embed.set_footer(text=f"Server: {message.guild.name}") - + await message.channel.send(embed=embed) return - + for key in responses.keys(): if key.lower() == trigger.lower(): removed_response = responses[key] del responses[key] self.guild_responses[guild_id] = responses self.save_responses() - - embed = discord.Embed( - title="✅ Auto-response Removed.", - color=0xff6b6b - ) + + embed = discord.Embed(title="✅ Auto-response Removed.", color=0xFF6B6B) embed.add_field(name="Trigger", value=f"`{key}`", inline=True) - embed.add_field(name="Response", value=f"`{removed_response}`", inline=True) + embed.add_field( + name="Response", value=f"`{removed_response}`", inline=True + ) embed.set_footer(text=f"Server: {message.guild.name}") - + await message.channel.send(embed=embed) return - - await message.channel.send(f"❌ No auto-response found for trigger: `{trigger}`") + + await message.channel.send( + f"❌ No auto-response found for trigger: `{trigger}`" + ) async def list_responses(self, message, guild_id): responses = self.get_responses(guild_id) - + if not responses: await message.channel.send("No auto-responses configured for this server.") return - + embed = discord.Embed( - title=f"Auto-Responses for {message.guild.name}", - color=0x3498db + title=f"Auto-Responses for {message.guild.name}", color=0x3498DB ) - + for i, (trigger, response) in enumerate(responses.items(), 1): display_response = response[:50] + "..." if len(response) > 50 else response embed.add_field( - name=f"{i}. `{trigger}`", - value=f"→ {display_response}", - inline=False + name=f"{i}. `{trigger}`", value=f"→ {display_response}", inline=False ) - + embed.set_footer(text=f"Total responses: {len(responses)}") await message.channel.send(embed=embed) From c491cb05632ba77caae69943909fd01e1cf8f4b9 Mon Sep 17 00:00:00 2001 From: crate Date: Thu, 5 Jun 2025 18:17:06 -0500 Subject: [PATCH 13/21] fix iputils embeds --- garfpy/iputils.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/garfpy/iputils.py b/garfpy/iputils.py index 1f69723..1d91554 100644 --- a/garfpy/iputils.py +++ b/garfpy/iputils.py @@ -38,8 +38,7 @@ class IPUtils: result = subprocess.run( ["ping", "-c", "4", target], capture_output=True, text=True ) - embed = discord.Embed(title=f"Ping result:", color=0x4D4D4D) - embed.add_field(name=target, value=f"```{result.stdout}```") + embed = discord.Embed(title="Ping result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") await message.channel.send(embed=embed) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") @@ -53,8 +52,7 @@ class IPUtils: result = subprocess.run( ["nslookup", target], capture_output=True, text=True ) - embed = discord.Embed(title=f"NSLookup result:", color=0x4D4D4D) - embed.add_field(name=target, value=f"```{result.stdout}```") + embed = discord.Embed(title="NSLookup result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") await message.channel.send(embed=embed) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") @@ -68,8 +66,7 @@ class IPUtils: result = subprocess.run( ["nmap", "-Pn", "-O", "-v", target], capture_output=True, text=True ) - embed = discord.Embed(title=f"Nmap scan result:", color=0x4D4D4D) - embed.add_field(name=target, value=f"```{result.stdout}```") + embed = discord.Embed(title="Nmap scan result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") embed.set_footer(text="https://nmap.org/") await message.channel.send(embed=embed) except Exception as e: From ef59f95bc0827a691dba1f9f1a003580b38dbab9 Mon Sep 17 00:00:00 2001 From: crate Date: Thu, 5 Jun 2025 18:21:19 -0500 Subject: [PATCH 14/21] fix iputils f strings --- garfpy/iputils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/garfpy/iputils.py b/garfpy/iputils.py index 1d91554..2e3a84f 100644 --- a/garfpy/iputils.py +++ b/garfpy/iputils.py @@ -38,7 +38,7 @@ class IPUtils: result = subprocess.run( ["ping", "-c", "4", target], capture_output=True, text=True ) - embed = discord.Embed(title="Ping result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") + embed = discord.Embed(title=f"Ping result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") await message.channel.send(embed=embed) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") @@ -52,7 +52,7 @@ class IPUtils: result = subprocess.run( ["nslookup", target], capture_output=True, text=True ) - embed = discord.Embed(title="NSLookup result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") + embed = discord.Embed(title=f"NSLookup result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") await message.channel.send(embed=embed) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") @@ -66,7 +66,7 @@ class IPUtils: result = subprocess.run( ["nmap", "-Pn", "-O", "-v", target], capture_output=True, text=True ) - embed = discord.Embed(title="Nmap scan result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") + embed = discord.Embed(title=f"Nmap scan result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") embed.set_footer(text="https://nmap.org/") await message.channel.send(embed=embed) except Exception as e: From 1f8fc09562a6e95e013c1b97093386139fe681a4 Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 00:41:52 -0500 Subject: [PATCH 15/21] update README --- README.md | 42 +++++++++++++++++++++++++++++++----------- garfpy/iputils.py | 18 +++++++++++++++--- garfpy/respond.py | 34 +++++++++++++++++----------------- 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 7ac0ede..f20b8ad 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,44 @@ Who is GarfBot? ====== ![garfield](https://www.crate.zip/garfield.png) -GarfBot is a discord bot that uses OpenAI's generative pre-trained models to produce text and images for your personal entertainment and companionship. There are a few ways you can interact with him on discord, either in a public server or by direct message: +GarfBot is a discord bot that uses OpenAI's generative pre-trained models to produce text and images for your personal entertainment and companionship. +
There are a few ways you can interact with him on discord, either in a public server or by direct message: `hey garfield {prompt}` -Responds with text. +
Responds with text. `garfpic {prompt}` -Responds with an image. +
Responds with an image. `garfping {target}` -Responds with iputils-ping result from target. +
Responds with iputils-ping result from target. `garfpic {target}` -Responds with dns lookup result from target. +
Responds with dns lookup result from target. `garfhack {target}` -Responds with nmap scan result from target. +
Responds with nmap scan result from target. `garfshop {item} {zip}` -Responds with 10 grocery {item}s from the nearest Kroger location, listed from least to most expensive. +
Responds with 10 grocery {item}s from the nearest Kroger location, listed from least to most expensive. + +`garfwiki {query}` +
Garfbot looks up a wikipedia article and will summarize it for you. + +`garfqr {text}` +
Create a QR code for any string up to 1000 characters. + +`garfbot response {add} {trigger} {response}` +
Add a GarfBot auto response for your server. Use "quotes" if you like. + +`garfbot response {remove} {trigger}` +
Remove a GarfBot auto response for your server. + +`garfbot response {list}` +
List current GarfBot auto responses for your server. + +`garfbot help` +
Show a list of these commands. Installation ====== @@ -38,7 +57,8 @@ GARFBOT_TOKEN = "Discord API token" OPENAI_TOKEN = "OpenAI API token" ``` -I recommend building a docker image using the included DockerFile as a template. Run the container binding /usr/src/app to GarfBot's CWD: +I recommend building a docker image using the included DockerFile as a template. +
Run the container binding /usr/src/app to GarfBot's CWD: ```console $ docker build -t garfbot . @@ -53,7 +73,7 @@ If you prefer to install dependencies on you own host and run as a systemd servi $ sudo nano /etc/systemd/system/garfbot.service ``` -Replace {user} with your username: +Replace $USER with your username: ```console [Unit] @@ -63,8 +83,8 @@ After=multi-user.target [Service] Type=simple Restart=always -User={user} -WorkingDirectory=/home/{user}/garfbot +User=$USER +WorkingDirectory=/home/$USER/garfbot ExecStart=/usr/bin/python garfbot.py [Install] diff --git a/garfpy/iputils.py b/garfpy/iputils.py index 2e3a84f..6a301d4 100644 --- a/garfpy/iputils.py +++ b/garfpy/iputils.py @@ -38,7 +38,11 @@ class IPUtils: result = subprocess.run( ["ping", "-c", "4", target], capture_output=True, text=True ) - embed = discord.Embed(title=f"Ping result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") + embed = discord.Embed( + title=f"Ping result: {target}", + color=0x4D4D4D, + description=f"```{result.stdout}```", + ) await message.channel.send(embed=embed) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") @@ -52,7 +56,11 @@ class IPUtils: result = subprocess.run( ["nslookup", target], capture_output=True, text=True ) - embed = discord.Embed(title=f"NSLookup result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") + embed = discord.Embed( + title=f"NSLookup result: {target}", + color=0x4D4D4D, + description=f"```{result.stdout}```", + ) await message.channel.send(embed=embed) except Exception as e: await message.channel.send(f"`GarfBot Error: {str(e)}`") @@ -66,7 +74,11 @@ class IPUtils: result = subprocess.run( ["nmap", "-Pn", "-O", "-v", target], capture_output=True, text=True ) - embed = discord.Embed(title=f"Nmap scan result: {target}", color=0x4D4D4D, description=f"```{result.stdout}```") + embed = discord.Embed( + title=f"Nmap scan result: {target}", + color=0x4D4D4D, + description=f"```{result.stdout}```", + ) embed.set_footer(text="https://nmap.org/") await message.channel.send(embed=embed) except Exception as e: diff --git a/garfpy/respond.py b/garfpy/respond.py index ab3e55d..d2c6293 100644 --- a/garfpy/respond.py +++ b/garfpy/respond.py @@ -7,47 +7,47 @@ import re class GarfbotRespond: def __init__(self): - self.guild_responses = {} + self.garfbot_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.guild_responses = json.load(f) - self.guild_responses = { - int(k): v for k, v in self.guild_responses.items() + self.garfbot_responses = json.load(f) + self.garfbot_responses = { + int(k): v for k, v in self.garfbot_responses.items() } total_responses = sum( - len(responses) for responses in self.guild_responses.values() + len(responses) for responses in self.garfbot_responses.values() ) logger.info( - f"Loaded responses for {len(self.guild_responses)} server(s), ({total_responses} total responses)" + f"Loaded responses for {len(self.garfbot_responses)} server(s), ({total_responses} total responses)" ) except Exception as e: logger.info(f"Error loading responses: {e}") - self.guild_responses = {} + self.garfbot_responses = {} else: - self.guild_responses = {} + self.garfbot_responses = {} def save_responses(self): try: - save_data = {str(k): v for k, v in self.guild_responses.items()} + save_data = {str(k): v for k, v in self.garfbot_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.guild_responses.values() + len(responses) for responses in self.garfbot_responses.values() ) logger.info( - f"Saved responses for {len(self.guild_responses)} servers ({total_responses} total responses)" + f"Saved responses for {len(self.garfbot_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.guild_responses: - self.guild_responses[guild_id] = {} - return self.guild_responses[guild_id] + if guild_id not in self.garfbot_responses: + self.garfbot_responses[guild_id] = {} + return self.garfbot_responses[guild_id] async def garfbot_response(self, message, content): guild_id = message.guild.id @@ -96,7 +96,7 @@ class GarfbotRespond: responses = self.get_responses(guild_id) responses[trigger] = response_text - self.guild_responses[guild_id] = responses + self.garfbot_responses[guild_id] = responses self.save_responses() embed = discord.Embed(title="✅ Auto-response Added.", color=0x00FF00) @@ -112,7 +112,7 @@ class GarfbotRespond: if trigger in responses: removed_response = responses[trigger] del responses[trigger] - self.guild_responses[guild_id] = responses + self.garfbot_responses[guild_id] = responses self.save_responses() embed = discord.Embed(title="✅ Auto-response Removed.", color=0xFF6B6B) @@ -127,7 +127,7 @@ class GarfbotRespond: if key.lower() == trigger.lower(): removed_response = responses[key] del responses[key] - self.guild_responses[guild_id] = responses + self.garfbot_responses[guild_id] = responses self.save_responses() embed = discord.Embed(title="✅ Auto-response Removed.", color=0xFF6B6B) From af1c689fe7ada38fc23b8e111b5600bafd2b8d5c Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 01:20:43 -0500 Subject: [PATCH 16/21] add garfbot help --- garfmain.py | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/garfmain.py b/garfmain.py index ac6224c..09eb682 100644 --- a/garfmain.py +++ b/garfmain.py @@ -107,6 +107,65 @@ async def on_message(message): ) await garfield.garfpic(message, prompt) + # GarfBot help + elif lower.strip() == "garfbot help": + embed = discord.Embed(title="**Need help?**", color=0x4D4D4D) + embed.add_field( + name="hey garfield `prompt`", value="*Responds with text.*", inline=True + ) + embed.add_field( + name="garfpic `prompt`", value="*Responds with an image.*", inline=True + ) + embed.add_field( + name="garfping `target`", + value="*Responds with iputils-ping result from target.*", + inline=True, + ) + embed.add_field( + name="garfpic `target`", + value="*Responds with dns lookup result from target.*", + inline=True, + ) + embed.add_field( + name="garfhack `target`", + value="*Responds with nmap scan result from target.*", + inline=True, + ) + embed.add_field( + name="garfwiki `query`", + value="*Garfbot looks up a wikipedia article and will summarize it for you.*", + inline=True, + ) + embed.add_field( + name="garfshop `item` `zip`", + value="*Responds with 10 grocery items from the nearest Kroger location, cheapest first.*", + inline=True, + ) + embed.add_field( + name="garfqr `text`", + value="*Create a QR code for any string up to 1000 characters.*", + inline=True, + ) + embed.add_field( + name="garfbot response `add` `trigger` `response`", + value='*Add a GarfBot auto response for your server. Use "quotes" if you like.*', + inline=True, + ) + embed.add_field( + name="garfbot response `remove` `trigger`", + value="*Remove a GarfBot auto response for your server.*", + inline=True, + ) + embed.add_field( + name="garfbot response `list`", + value="*List current GarfBot auto responses for your server.*", + inline=True, + ) + embed.add_field( + name="garfbot help", value="*Show a list of these commands.*", inline=True + ) + await message.channel.send(embed=embed) + # Army of Dawn Server only!! elif message.guild and message.guild.id == 719605634772893757: await aod_message(garfbot, message) @@ -125,7 +184,7 @@ async def on_message(message): break -# Run Garfbot +# Run GarfBot async def garfbot_connect(): while True: try: From aecafc53fce9f9a2200b24c6c5a2d9a5a0d90785 Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 01:22:29 -0500 Subject: [PATCH 17/21] fix garfbot help --- README.md | 2 +- garfmain.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f20b8ad..d750de1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ GarfBot is a discord bot that uses OpenAI's generative pre-trained models to pro `garfping {target}`
Responds with iputils-ping result from target. -`garfpic {target}` +`garfdns {target}`
Responds with dns lookup result from target. `garfhack {target}` diff --git a/garfmain.py b/garfmain.py index 09eb682..ed8d9fb 100644 --- a/garfmain.py +++ b/garfmain.py @@ -122,7 +122,7 @@ async def on_message(message): inline=True, ) embed.add_field( - name="garfpic `target`", + name="garfdns `target`", value="*Responds with dns lookup result from target.*", inline=True, ) From 9f9a484613dcd2ee534841fd780fbaa1c73af44f Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 16:39:10 -0500 Subject: [PATCH 18/21] change Dockerfile to use python alpine img --- Dockerfile | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 534855c..a658fbf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,29 @@ -FROM python:3.11.10-bookworm - +FROM python:3.11.10-alpine WORKDIR /usr/src/app -RUN apt update -RUN apt install -y iputils-ping -RUN apt install -y dnsutils -RUN apt install -y nmap -RUN apt install -y python3 -RUN apt install -y python3-pip -RUN pip3 install discord -RUN pip3 install openai -RUN pip3 install aiohttp -RUN pip3 install requests -RUN pip3 install wikipedia -RUN pip3 install pillow -RUN pip3 install qrcode +RUN apk update && \ + apk add --no-cache \ + iputils \ + bind-tools \ + nmap \ + gcc \ + musl-dev \ + jpeg-dev \ + zlib-dev \ + freetype-dev \ + lcms2-dev \ + openjpeg-dev \ + tiff-dev \ + tk-dev \ + tcl-dev -CMD [ "python", "garfmain.py" ] +RUN pip3 install --no-cache-dir \ + discord \ + openai \ + aiohttp \ + requests \ + wikipedia \ + pillow \ + qrcode + +CMD [ "python", "garfmain.py" ] \ No newline at end of file From 00bc9db5b3be4008e0ddb7b3631573b32eaab5c1 Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 16:44:04 -0500 Subject: [PATCH 19/21] hopefully this fixes it --- .gitea/workflows/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index b4ffe96..274a6c6 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -22,7 +22,7 @@ jobs: docker stop garfbot docker rm garfbot docker build -t git.crate.zip/crate/garfbot:latest . - docker run -d --restart always -v $PWD:/usr/src/app --name garfbot git.crate.zip/crate/garfbot:latest + docker run -d --restart always -v /home/crate/garfbot:/usr/src/app --name garfbot git.crate.zip/crate/garfbot:latest else docker restart garfbot fi From b63e1738d1544be8732754cce1f94d57262f9672 Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 17:20:43 -0500 Subject: [PATCH 20/21] add docker compose and update pipeline --- .gitea/workflows/deploy.yaml | 2 +- docker-compose.yml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yml diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 274a6c6..732a476 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -22,7 +22,7 @@ jobs: docker stop garfbot docker rm garfbot docker build -t git.crate.zip/crate/garfbot:latest . - docker run -d --restart always -v /home/crate/garfbot:/usr/src/app --name garfbot git.crate.zip/crate/garfbot:latest + docker compose up -d else docker restart garfbot fi diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..4066fb3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,7 @@ +services: + garfbot: + image: git.crate.zip/crate/garfbot:latest + container_name: garfbot + restart: always + volumes: + - /home/crate/garfbot:/usr/src/app From bdc3c13edbed1dcceb6b67a7510765ef6ca21f74 Mon Sep 17 00:00:00 2001 From: crate Date: Fri, 6 Jun 2025 17:36:28 -0500 Subject: [PATCH 21/21] rename cicd compose stack --- .gitea/workflows/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 732a476..7dfaf5c 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -22,7 +22,7 @@ jobs: docker stop garfbot docker rm garfbot docker build -t git.crate.zip/crate/garfbot:latest . - docker compose up -d + docker compose up -d -p garfbot else docker restart garfbot fi