Compare commits

...

7 Commits

Author SHA1 Message Date
13d2f09fe3 Merge pull request 'switch to commands bot' (#8) from commands-bot into main
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 16s
Reviewed-on: #8

hopefully this works!
2025-06-07 22:53:29 +00:00
97f90e3814 switch to commands bot 2025-06-07 17:52:20 -05:00
aa3996bbd9 fix deployment script
All checks were successful
Garfbot CI/CD Deployment / Deploy (push) Successful in 6s
2025-06-07 13:43:14 -05:00
c71a55da2f fix deployment script
Some checks failed
Garfbot CI/CD Deployment / Deploy (push) Failing after 4s
2025-06-07 13:38:16 -05:00
7f0ce32eea fix deployment script
Some checks failed
Garfbot CI/CD Deployment / Deploy (push) Failing after 4s
2025-06-07 13:34:59 -05:00
e34469a755 Merge pull request 'move help and update dockerfile' (#7) from help into main
Some checks failed
Garfbot CI/CD Deployment / Deploy (push) Failing after 2m3s
Reviewed-on: #7

looks good
2025-06-07 18:08:30 +00:00
e1707e789c move help and update dockerfile 2025-06-07 13:07:26 -05:00
8 changed files with 219 additions and 191 deletions

View File

@ -8,21 +8,20 @@ jobs:
Deploy:
container:
volumes:
- /home/crate/garfbot:/workspace/crate/garfbot/deploy
- /home/crate/garfbot:/workspace/crate/garfbot/garfbot
steps:
- name: Pull Garfbot and restart container
run: |
cd /workspace/crate/garfbot/deploy
git pull origin main
- name: Pull GarfBot and re-deploy
run: |
cd /workspace/crate/garfbot/garfbot
git pull origin main
CHANGED=$(git diff --name-only HEAD~1 HEAD)
CHANGED=$(git diff --name-only HEAD~1 HEAD)
if echo "$CHANGED" | grep -qE "(Dockerfile|requirements\.txt|docker-compose\.yml)"; then
docker stop garfbot
docker rm garfbot
docker build -t git.crate.zip/crate/garfbot:latest .
docker compose up -d -p garfbot
else
docker restart garfbot
fi
if echo "$CHANGED" | grep -qE "(Dockerfile|requirements\.txt|docker-compose\.yml|\.gitea/workflows/deploy\.yaml)"; then
docker compose down
docker build -t git.crate.zip/crate/garfbot:latest .
docker compose up -d
else
docker restart garfbot
fi

View File

@ -1,4 +1,4 @@
FROM python:3.11.10-alpine
FROM python:alpine
WORKDIR /usr/src/app
RUN apk update && \

View File

@ -1,8 +1,10 @@
import config
import asyncio
import discord
from discord.ext import commands
from garfpy import (
help,
logger,
IPUtils,
aod_message,
@ -23,7 +25,8 @@ intents = discord.Intents.default()
intents.members = True
intents.messages = True
intents.message_content = True
garfbot = discord.Client(intents=intents)
garfbot = commands.Bot(command_prefix=["garfbot ", "garf", "$"], intents=intents)
garf_respond = GarfbotRespond()
garfield = GarfAI()
@ -38,12 +41,82 @@ async def on_ready():
garf_respond.load_responses()
asyncio.create_task(garfield.process_image_requests())
logger.info(
f"Logged in as {garfbot.user.name} running {txtmodel} and {imgmodel}."
f"Logged in as {garfbot.user.name} running {txtmodel} and {imgmodel}." # type: ignore
)
except Exception as e:
logger.error(e)
@garfbot.command(name="ping")
async def ping(ctx, *, target):
"""Ping a target"""
logger.info(
f"Ping Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Target: {target}"
)
await iputils.ping(ctx, target)
@garfbot.command(name="dns")
async def dns(ctx, *, target):
"""DNS lookup for a target"""
logger.info(
f"NSLookup Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Target: {target}"
)
await iputils.dns(ctx, target)
@garfbot.command(name="hack")
async def hack(ctx, *, target):
"""Nmap scan a target"""
logger.info(
f"Nmap Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Target: {target}"
)
await iputils.scan(ctx, target)
@garfbot.command(name="qr")
async def garfbot_qr(ctx, *, text):
logger.info(
f"QR Code Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Text: {text}"
)
if len(text) > 1000:
await ctx.send("❌ Text too long! Maximum 1000 characters.")
else:
try:
qr_code = await generate_qr(text)
sendfile = discord.File(fp=qr_code, filename="qrcode.png")
await ctx.send(file=sendfile)
except Exception as e:
logger.error(e)
await ctx.send(e)
@garfbot.command(name="wiki")
async def garfbot_wiki(ctx, *, query):
summary = await garfield.wikisum(query)
await ctx.send(summary)
@garfbot.command(name="shop")
async def garfbot_shop(ctx, *, query):
try:
response = kroger.garfshop(query)
await ctx.send(response)
except Exception as e:
await ctx.send(f"`GarfBot Error: {str(e)}`")
@garfbot.command(name="weather")
async def garfbot_weather(ctx, *, location):
embed = await weather.weather(location)
await ctx.send(embed=embed)
# @garfbot.command(name="help")
# async def garfbot_help(ctx):
# await help(ctx)
@garfbot.event
async def on_message(message):
if message.author == garfbot.user:
@ -55,41 +128,8 @@ async def on_message(message):
guild_id = message.guild.id
guild_name = message.guild.name if message.guild else "Direct Message"
# 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 "):
query = message.content[9:]
summary = await garfield.wikisum(query)
await message.channel.send(summary)
# QR codes
if lower.startswith("garfqr "):
text = message.content[7:]
if len(text) > 1000:
await message.channel.send("❌ Text too long! Maximum 1000 characters.")
else:
try:
qr_code = await generate_qr(text)
sendfile = discord.File(fp=qr_code, filename="qrcode.png")
await message.channel.send(file=sendfile)
except Exception as e:
logger.error(e)
await message.channel.send(e)
# Kroger Shopping
if lower.startswith("garfshop "):
try:
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)}`")
# Chats & pics
elif lower.startswith("hey garfield") or isinstance(
if lower.startswith("hey garfield") or isinstance(
message.channel, discord.DMChannel
):
prompt = content[12:] if lower.startswith("hey garfield") else message.content
@ -99,7 +139,7 @@ async def on_message(message):
)
await message.channel.send(answer)
elif lower.startswith("garfpic "):
if lower.startswith("garfpic "):
prompt = content[8:]
logger.info(
f"Image Request - User: {user_name}, Server: {guild_name}, Prompt: {prompt}"
@ -109,70 +149,9 @@ async def on_message(message):
)
await garfield.garfpic(message, prompt)
# Weather
elif lower.startswith("garfbot weather "):
location = lower[16:]
embed = await weather.weather(location)
await message.channel.send(embed=embed)
# 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="garfdns `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)
await help(message)
# Army of Dawn Server only!!
elif message.guild and message.guild.id == 719605634772893757:
@ -191,6 +170,8 @@ async def on_message(message):
await message.channel.send(response)
break
await garfbot.process_commands(message)
# Run GarfBot
async def garfbot_connect():

View File

@ -1,6 +1,7 @@
# garfpy/__init__.py
from .log import logger
from .help import help
from .kroger import Kroger
from .kroger import Kroger
from .garfai import GarfAI

60
garfpy/help.py Normal file
View File

@ -0,0 +1,60 @@
import discord
async def help(message):
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="garfdns `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)

View File

@ -1,7 +1,6 @@
import discord
import ipaddress
import subprocess
from garfpy import logger
class IPUtils:
@ -23,63 +22,52 @@ class IPUtils:
return True
return False
async def scan(self, message, user, guild, query):
split = query.split()
target = split[-1]
async def ping(self, ctx, target):
if self.is_private(target):
return
try:
await ctx.send(f"`Pinging {target}...`")
result = subprocess.run(
["ping", "-c", "4", target], capture_output=True, text=True
)
embed = discord.Embed(
title=f"Ping result: {target}",
color=discord.Color.light_gray(),
description=f"```{result.stdout}```",
)
await ctx.send(embed=embed)
except Exception as e:
await ctx.send(f"`GarfBot Error: {str(e)}`")
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: {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)}`")
async def dns(self, ctx, target):
if self.is_private(target):
return
try:
await ctx.send(f"`Requesting {target}...`")
result = subprocess.run(
["nslookup", target], capture_output=True, text=True
)
embed = discord.Embed(
title=f"NSLookup result: {target}",
color=discord.Color.light_gray(),
description=f"```{result.stdout}```",
)
await ctx.send(embed=embed)
except Exception as e:
await ctx.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: {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)}`")
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: {target}",
color=0x4D4D4D,
description=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)}`")
async def scan(self, ctx, target):
try:
await ctx.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: {target}",
color=discord.Color.light_gray(),
description=f"```{result.stdout}```",
)
embed.set_footer(text="https://nmap.org/")
await ctx.send(embed=embed)
except Exception as e:
await ctx.send(f"`GarfBot Error: {str(e)}`")

View File

@ -47,7 +47,7 @@ async def generate_qr(text):
qr = qrcode.QRCode(
version=version,
error_correction=qrcode.constants.ERROR_CORRECT_L,
error_correction=qrcode.constants.ERROR_CORRECT_L, # type: ignore
box_size=box_size,
border=4,
)
@ -58,7 +58,7 @@ async def generate_qr(text):
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") # type: ignore
img_buffer.seek(0)
return img_buffer

View File

@ -18,13 +18,13 @@ class WeatherAPI:
else:
return {"zip": location}
parts = location.split()
params = location.split()
if len(parts) == 1:
return {"q": f"{parts[0]},US"}
if len(params) == 1:
return {"q": f"{params[0]},US"}
elif len(parts) == 2:
city, second = parts
elif len(params) == 2:
city, second = params
if len(second) == 2 and second.upper() not in [
"AK",
@ -88,27 +88,26 @@ class WeatherAPI:
else:
return {"q": f"{city},{second},US"}
elif len(parts) == 3:
city, state, country = parts
elif len(params) == 3:
city, state, country = params
return {"q": f"{city},{state},{country.upper()}"}
else:
# Check if last part looks like country code
if len(parts[-1]) == 2:
city_parts = parts[:-1]
country = parts[-1]
if len(params[-1]) == 2:
city_parts = params[:-1]
country = params[-1]
city_name = " ".join(city_parts)
return {"q": f"{city_name},{country.upper()}"}
elif len(parts) >= 2 and len(parts[-1]) == 2 and len(parts[-2]) <= 2:
city_parts = parts[:-2]
state = parts[-2]
country = parts[-1]
elif len(params) >= 2 and len(params[-1]) == 2 and len(params[-2]) <= 2:
city_parts = params[:-2]
state = params[-2]
country = params[-1]
city_name = " ".join(city_parts)
return {"q": f"{city_name},{state},{country.upper()}"}
else:
city_name = " ".join(parts)
city_name = " ".join(params)
return {"q": f"{city_name},US"}
async def get_weather(self, location, units="metric"):