Compare commits
11 Commits
6ec931265f
...
main
Author | SHA1 | Date | |
---|---|---|---|
b14261a8d4 | |||
8c08eed842 | |||
13d2f09fe3 | |||
97f90e3814 | |||
aa3996bbd9 | |||
c71a55da2f | |||
7f0ce32eea | |||
e34469a755 | |||
e1707e789c | |||
a53a81a180 | |||
4d2a0dc2d9 |
@ -8,21 +8,20 @@ jobs:
|
|||||||
Deploy:
|
Deploy:
|
||||||
container:
|
container:
|
||||||
volumes:
|
volumes:
|
||||||
- /home/crate/garfbot:/workspace/crate/garfbot/deploy
|
- /home/crate/garfbot:/workspace/crate/garfbot/garfbot
|
||||||
steps:
|
steps:
|
||||||
- name: Pull Garfbot and restart container
|
- name: Pull GarfBot and re-deploy
|
||||||
run: |
|
run: |
|
||||||
cd /workspace/crate/garfbot/deploy
|
cd /workspace/crate/garfbot/garfbot
|
||||||
|
|
||||||
git pull origin main
|
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|\.gitea/workflows/deploy\.yaml)"; then
|
||||||
|
docker compose down
|
||||||
if echo "$CHANGED" | grep -qE "(Dockerfile|requirements\.txt|docker-compose\.yml)"; then
|
docker build -t git.crate.zip/crate/garfbot:latest .
|
||||||
docker stop garfbot
|
docker compose up -d
|
||||||
docker rm garfbot
|
else
|
||||||
docker build -t git.crate.zip/crate/garfbot:latest .
|
docker restart garfbot
|
||||||
docker compose up -d -p garfbot
|
fi
|
||||||
else
|
|
||||||
docker restart garfbot
|
|
||||||
fi
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11.10-alpine
|
FROM python:alpine
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
RUN apk update && \
|
RUN apk update && \
|
||||||
|
224
garfmain.py
224
garfmain.py
@ -1,8 +1,10 @@
|
|||||||
import config
|
import config
|
||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
from garfpy import (
|
from garfpy import (
|
||||||
|
help,
|
||||||
logger,
|
logger,
|
||||||
IPUtils,
|
IPUtils,
|
||||||
aod_message,
|
aod_message,
|
||||||
@ -10,6 +12,7 @@ from garfpy import (
|
|||||||
Kroger,
|
Kroger,
|
||||||
GarfAI,
|
GarfAI,
|
||||||
GarfbotRespond,
|
GarfbotRespond,
|
||||||
|
WeatherAPI,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -22,12 +25,15 @@ intents = discord.Intents.default()
|
|||||||
intents.members = True
|
intents.members = True
|
||||||
intents.messages = True
|
intents.messages = True
|
||||||
intents.message_content = True
|
intents.message_content = True
|
||||||
garfbot = discord.Client(intents=intents)
|
|
||||||
|
garfbot = commands.Bot(command_prefix=["garfbot ", "garf", "$"], intents=intents)
|
||||||
|
garfbot.remove_command("help")
|
||||||
|
|
||||||
garf_respond = GarfbotRespond()
|
garf_respond = GarfbotRespond()
|
||||||
garfield = GarfAI()
|
garfield = GarfAI()
|
||||||
iputils = IPUtils()
|
iputils = IPUtils()
|
||||||
kroger = Kroger()
|
kroger = Kroger()
|
||||||
|
weather = WeatherAPI()
|
||||||
|
|
||||||
|
|
||||||
@garfbot.event
|
@garfbot.event
|
||||||
@ -36,12 +42,99 @@ async def on_ready():
|
|||||||
garf_respond.load_responses()
|
garf_respond.load_responses()
|
||||||
asyncio.create_task(garfield.process_image_requests())
|
asyncio.create_task(garfield.process_image_requests())
|
||||||
logger.info(
|
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:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
|
|
||||||
|
@garfbot.command(name="ping")
|
||||||
|
async def garfbot_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 garfbot_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 garfbot_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):
|
||||||
|
await weather.weather(ctx, location)
|
||||||
|
|
||||||
|
|
||||||
|
@garfbot.command(name="chat")
|
||||||
|
async def garfchat(ctx, *, prompt):
|
||||||
|
answer = await garfield.generate_chat(prompt)
|
||||||
|
logger.info(
|
||||||
|
f"Chat Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
|
||||||
|
)
|
||||||
|
await ctx.send(answer)
|
||||||
|
|
||||||
|
|
||||||
|
@garfbot.command(name="pic")
|
||||||
|
async def garfpic(ctx, *, prompt):
|
||||||
|
logger.info(
|
||||||
|
f"Image Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
|
||||||
|
)
|
||||||
|
await ctx.send(f"`Please wait... image generation queued: {prompt}`")
|
||||||
|
await garfield.garfpic(ctx, prompt)
|
||||||
|
|
||||||
|
|
||||||
|
@garfbot.command(name="help")
|
||||||
|
async def garfbot_help(ctx):
|
||||||
|
await help(ctx)
|
||||||
|
|
||||||
|
|
||||||
@garfbot.event
|
@garfbot.event
|
||||||
async def on_message(message):
|
async def on_message(message):
|
||||||
if message.author == garfbot.user:
|
if message.author == garfbot.user:
|
||||||
@ -49,129 +142,22 @@ async def on_message(message):
|
|||||||
|
|
||||||
content = message.content.strip()
|
content = message.content.strip()
|
||||||
lower = content.lower()
|
lower = content.lower()
|
||||||
user_name = message.author.name
|
|
||||||
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
|
# Chats & pics
|
||||||
elif lower.startswith("hey garfield") or isinstance(
|
if lower.startswith("hey garfield") or isinstance(
|
||||||
message.channel, discord.DMChannel
|
message.channel, discord.DMChannel
|
||||||
):
|
):
|
||||||
prompt = content[12:] if lower.startswith("hey garfield") else message.content
|
ctx = await garfbot.get_context(message)
|
||||||
answer = await garfield.generate_chat(prompt)
|
await garfchat(ctx, prompt=content)
|
||||||
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)
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Army of Dawn Server only!!
|
|
||||||
elif message.guild and message.guild.id == 719605634772893757:
|
|
||||||
await aod_message(garfbot, message)
|
|
||||||
|
|
||||||
# Auto-responses
|
# Auto-responses
|
||||||
elif message.guild:
|
elif message.guild:
|
||||||
|
guild_id = message.guild.id
|
||||||
|
|
||||||
|
# Army of Dawn Server only!!
|
||||||
|
if guild_id == 719605634772893757:
|
||||||
|
await aod_message(garfbot, message)
|
||||||
|
|
||||||
responses = garf_respond.get_responses(guild_id)
|
responses = garf_respond.get_responses(guild_id)
|
||||||
|
|
||||||
if lower.startswith("garfbot response "):
|
if lower.startswith("garfbot response "):
|
||||||
@ -183,6 +169,8 @@ async def on_message(message):
|
|||||||
await message.channel.send(response)
|
await message.channel.send(response)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
await garfbot.process_commands(message)
|
||||||
|
|
||||||
|
|
||||||
# Run GarfBot
|
# Run GarfBot
|
||||||
async def garfbot_connect():
|
async def garfbot_connect():
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
# garfpy/__init__.py
|
# garfpy/__init__.py
|
||||||
|
|
||||||
from .log import logger
|
from .log import logger
|
||||||
|
from .help import help
|
||||||
from .kroger import Kroger
|
from .kroger import Kroger
|
||||||
from .kroger import Kroger
|
from .kroger import Kroger
|
||||||
from .garfai import GarfAI
|
from .garfai import GarfAI
|
||||||
@ -8,3 +9,4 @@ from .respond import GarfbotRespond
|
|||||||
from .aod import aod_message
|
from .aod import aod_message
|
||||||
from .qr import generate_qr
|
from .qr import generate_qr
|
||||||
from .iputils import IPUtils
|
from .iputils import IPUtils
|
||||||
|
from .weather import WeatherAPI
|
@ -16,17 +16,15 @@ class GarfAI:
|
|||||||
self.imgmodel = config.IMG_MODEL
|
self.imgmodel = config.IMG_MODEL
|
||||||
self.image_request_queue = asyncio.Queue()
|
self.image_request_queue = asyncio.Queue()
|
||||||
|
|
||||||
async def garfpic(self, message, prompt):
|
async def garfpic(self, ctx, prompt):
|
||||||
await self.image_request_queue.put({"message": message, "prompt": prompt})
|
await self.image_request_queue.put({"ctx": ctx, "prompt": prompt})
|
||||||
|
|
||||||
async def generate_image(self, prompt):
|
async def generate_image(self, prompt):
|
||||||
|
client = AsyncOpenAI(api_key=self.openaikey)
|
||||||
try:
|
try:
|
||||||
client = AsyncOpenAI(api_key=self.openaikey)
|
|
||||||
response = await client.images.generate(
|
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
|
|
||||||
except openai.BadRequestError as e:
|
except openai.BadRequestError as e:
|
||||||
return f"`GarfBot Error: ({e.status_code}) - Your request was rejected as a result of our safety system.`"
|
return f"`GarfBot Error: ({e.status_code}) - Your request was rejected as a result of our safety system.`"
|
||||||
except openai.InternalServerError as e:
|
except openai.InternalServerError as e:
|
||||||
@ -35,32 +33,48 @@ class GarfAI:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
return "`GarfBot Error: Lasagna`"
|
return "`GarfBot Error: Lasagna`"
|
||||||
|
data = getattr(response, "data", None)
|
||||||
|
if not data:
|
||||||
|
logger.error("No data in response")
|
||||||
|
return "`GarfBot Error: No images generated`"
|
||||||
|
|
||||||
|
first_image = data[0] if len(data) > 0 else None
|
||||||
|
if not first_image:
|
||||||
|
logger.error("No image in response data")
|
||||||
|
return "`GarfBot Error: No images generated`"
|
||||||
|
|
||||||
|
image_url = getattr(first_image, "url", None)
|
||||||
|
if not image_url:
|
||||||
|
logger.error("No URL in image response")
|
||||||
|
return "`GarfBot Error: No image URL returned`"
|
||||||
|
|
||||||
|
return image_url
|
||||||
|
|
||||||
async def process_image_requests(self):
|
async def process_image_requests(self):
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
while True:
|
while True:
|
||||||
request = await self.image_request_queue.get()
|
request = await self.image_request_queue.get()
|
||||||
message = request["message"]
|
ctx = request["ctx"]
|
||||||
prompt = request["prompt"]
|
prompt = request["prompt"]
|
||||||
image_url = await self.generate_image(prompt)
|
image_url = await self.generate_image(prompt)
|
||||||
if "GarfBot Error" not in image_url:
|
if image_url and "GarfBot Error" not in image_url:
|
||||||
logger.info("Downloading & sending image...")
|
logger.info("Downloading & sending image...")
|
||||||
async with session.get(image_url) as resp:
|
async with session.get(image_url) as resp:
|
||||||
if resp.status == 200:
|
if resp.status == 200:
|
||||||
image_data = await resp.read()
|
image_data = await resp.read()
|
||||||
ram_image = io.BytesIO(image_data)
|
image = io.BytesIO(image_data)
|
||||||
ram_image.seek(0)
|
image.seek(0)
|
||||||
timestamp = message.created_at.strftime("%Y%m%d%H%M%S")
|
timestamp = ctx.message.created_at.strftime("%Y%m%d%H%M%S")
|
||||||
filename = f"{timestamp}_generated_image.png"
|
filename = f"{timestamp}_generated_image.png"
|
||||||
sendfile = discord.File(fp=ram_image, filename=filename)
|
sendfile = discord.File(fp=image, filename=filename)
|
||||||
try:
|
try:
|
||||||
await message.channel.send(file=sendfile)
|
await ctx.send(file=sendfile)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
else:
|
else:
|
||||||
await message.channel.send("`GarfBot Error: Odie`")
|
await ctx.send("`GarfBot Error: Odie`")
|
||||||
else:
|
else:
|
||||||
await message.channel.send(image_url)
|
await ctx.send(image_url)
|
||||||
self.image_request_queue.task_done()
|
self.image_request_queue.task_done()
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
@ -78,15 +92,16 @@ class GarfAI:
|
|||||||
],
|
],
|
||||||
max_tokens=400,
|
max_tokens=400,
|
||||||
)
|
)
|
||||||
answer = response.choices[0].message.content
|
answer = str(response.choices[0].message.content)
|
||||||
return answer.replace("an AI language model", "a cartoon animal")
|
return answer.replace("an AI language model", "a cartoon animal")
|
||||||
except openai.BadRequestError as e:
|
except openai.BadRequestError as e:
|
||||||
|
logger.error(e)
|
||||||
return f"`GarfBot Error: {e}`"
|
return f"`GarfBot Error: {e}`"
|
||||||
except openai.APIError as e:
|
except openai.APIError as e:
|
||||||
logger.info(e, flush=True)
|
logger.error(e)
|
||||||
return "`GarfBot Error: Monday`"
|
return "`GarfBot Error: Monday`"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.info(e, flush=True)
|
logger.error(e)
|
||||||
return "`GarfBot Error: Lasagna`"
|
return "`GarfBot Error: Lasagna`"
|
||||||
|
|
||||||
async def wikisum(self, query):
|
async def wikisum(self, query):
|
||||||
|
60
garfpy/help.py
Normal file
60
garfpy/help.py
Normal 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)
|
@ -1,7 +1,6 @@
|
|||||||
import discord
|
import discord
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import subprocess
|
import subprocess
|
||||||
from garfpy import logger
|
|
||||||
|
|
||||||
|
|
||||||
class IPUtils:
|
class IPUtils:
|
||||||
@ -23,63 +22,52 @@ class IPUtils:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def scan(self, message, user, guild, query):
|
async def ping(self, ctx, target):
|
||||||
split = query.split()
|
|
||||||
target = split[-1]
|
|
||||||
if self.is_private(target):
|
if self.is_private(target):
|
||||||
return
|
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 "):
|
async def dns(self, ctx, target):
|
||||||
try:
|
if self.is_private(target):
|
||||||
logger.info(
|
return
|
||||||
f"Ping Request - User: {user}, Server: {guild}, Target: {target}"
|
try:
|
||||||
)
|
await ctx.send(f"`Requesting {target}...`")
|
||||||
await message.channel.send(f"`Pinging {target}...`")
|
result = subprocess.run(
|
||||||
result = subprocess.run(
|
["nslookup", target], capture_output=True, text=True
|
||||||
["ping", "-c", "4", target], capture_output=True, text=True
|
)
|
||||||
)
|
embed = discord.Embed(
|
||||||
embed = discord.Embed(
|
title=f"NSLookup result: {target}",
|
||||||
title=f"Ping result: {target}",
|
color=discord.Color.light_gray(),
|
||||||
color=0x4D4D4D,
|
description=f"```{result.stdout}```",
|
||||||
description=f"```{result.stdout}```",
|
)
|
||||||
)
|
await ctx.send(embed=embed)
|
||||||
await message.channel.send(embed=embed)
|
except Exception as e:
|
||||||
except Exception as e:
|
await ctx.send(f"`GarfBot Error: {str(e)}`")
|
||||||
await message.channel.send(f"`GarfBot Error: {str(e)}`")
|
|
||||||
|
|
||||||
if query.startswith("garfdns "):
|
async def scan(self, ctx, target):
|
||||||
try:
|
try:
|
||||||
logger.info(
|
await ctx.send(f"`Scanning {target}...`")
|
||||||
f"NSLookup Request - User: {user}, Server: {guild}, Target: {target}"
|
result = subprocess.run(
|
||||||
)
|
["nmap", "-Pn", "-O", "-v", target], capture_output=True, text=True
|
||||||
await message.channel.send(f"`Requesting {target}...`")
|
)
|
||||||
result = subprocess.run(
|
embed = discord.Embed(
|
||||||
["nslookup", target], capture_output=True, text=True
|
title=f"Nmap scan result: {target}",
|
||||||
)
|
color=discord.Color.light_gray(),
|
||||||
embed = discord.Embed(
|
description=f"```{result.stdout}```",
|
||||||
title=f"NSLookup result: {target}",
|
)
|
||||||
color=0x4D4D4D,
|
embed.set_footer(text="https://nmap.org/")
|
||||||
description=f"```{result.stdout}```",
|
await ctx.send(embed=embed)
|
||||||
)
|
except Exception as e:
|
||||||
await message.channel.send(embed=embed)
|
await ctx.send(f"`GarfBot Error: {str(e)}`")
|
||||||
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)}`")
|
|
||||||
|
@ -47,7 +47,7 @@ async def generate_qr(text):
|
|||||||
|
|
||||||
qr = qrcode.QRCode(
|
qr = qrcode.QRCode(
|
||||||
version=version,
|
version=version,
|
||||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
error_correction=qrcode.constants.ERROR_CORRECT_L, # type: ignore
|
||||||
box_size=box_size,
|
box_size=box_size,
|
||||||
border=4,
|
border=4,
|
||||||
)
|
)
|
||||||
@ -58,7 +58,7 @@ async def generate_qr(text):
|
|||||||
qr_image = qr.make_image(fill_color="black", back_color="white")
|
qr_image = qr.make_image(fill_color="black", back_color="white")
|
||||||
|
|
||||||
img_buffer = BytesIO()
|
img_buffer = BytesIO()
|
||||||
qr_image.save(img_buffer, format="PNG")
|
qr_image.save(img_buffer, format="PNG") # type: ignore
|
||||||
img_buffer.seek(0)
|
img_buffer.seek(0)
|
||||||
|
|
||||||
return img_buffer
|
return img_buffer
|
||||||
|
@ -1,142 +1,204 @@
|
|||||||
import discord
|
import discord
|
||||||
import requests
|
import aiohttp
|
||||||
import json
|
import config
|
||||||
|
from garfpy import logger
|
||||||
|
|
||||||
|
|
||||||
class WeatherAPI:
|
class WeatherAPI:
|
||||||
def __init__(self, api_key):
|
def __init__(self, api_key=None):
|
||||||
self.api_key = api_key
|
self.api_key = api_key or config.WEATHER_TOKEN
|
||||||
self.base_url = "https://api.openweathermap.org/data/2.5/weather"
|
self.base_url = "https://api.openweathermap.org/data/2.5/weather"
|
||||||
|
|
||||||
def get_weather_by_zip(self, zip_code, country_code='US', units='metric'):
|
def parse_location(self, location):
|
||||||
"""
|
location = location.strip().lower()
|
||||||
Get weather data by zip code
|
|
||||||
|
if location.isdigit():
|
||||||
Args:
|
if len(location) == 5:
|
||||||
zip_code (str): ZIP code
|
return {"zip": f"{location},US"}
|
||||||
country_code (str): Country code (default: 'US')
|
else:
|
||||||
units (str): 'metric', 'imperial', or 'standard'
|
return {"zip": location}
|
||||||
"""
|
|
||||||
|
params = location.split()
|
||||||
|
|
||||||
|
if len(params) == 1:
|
||||||
|
return {"q": f"{params[0]},US"}
|
||||||
|
|
||||||
|
elif len(params) == 2:
|
||||||
|
city, second = params
|
||||||
|
|
||||||
|
if len(second) == 2 and second.upper() not in [
|
||||||
|
"AK",
|
||||||
|
"AL",
|
||||||
|
"AR",
|
||||||
|
"AZ",
|
||||||
|
"CA",
|
||||||
|
"CO",
|
||||||
|
"CT",
|
||||||
|
"DE",
|
||||||
|
"FL",
|
||||||
|
"GA",
|
||||||
|
"HI",
|
||||||
|
"IA",
|
||||||
|
"ID",
|
||||||
|
"IL",
|
||||||
|
"IN",
|
||||||
|
"KS",
|
||||||
|
"KY",
|
||||||
|
"LA",
|
||||||
|
"MA",
|
||||||
|
"MD",
|
||||||
|
"ME",
|
||||||
|
"MI",
|
||||||
|
"MN",
|
||||||
|
"MO",
|
||||||
|
"MS",
|
||||||
|
"MT",
|
||||||
|
"NC",
|
||||||
|
"ND",
|
||||||
|
"NE",
|
||||||
|
"NH",
|
||||||
|
"NJ",
|
||||||
|
"NM",
|
||||||
|
"NV",
|
||||||
|
"NY",
|
||||||
|
"OH",
|
||||||
|
"OK",
|
||||||
|
"OR",
|
||||||
|
"PA",
|
||||||
|
"RI",
|
||||||
|
"SC",
|
||||||
|
"SD",
|
||||||
|
"TN",
|
||||||
|
"TX",
|
||||||
|
"UT",
|
||||||
|
"VA",
|
||||||
|
"VT",
|
||||||
|
"WA",
|
||||||
|
"WI",
|
||||||
|
"WV",
|
||||||
|
"WY",
|
||||||
|
"DC",
|
||||||
|
"AS",
|
||||||
|
"GU",
|
||||||
|
"MP",
|
||||||
|
"PR",
|
||||||
|
"VI",
|
||||||
|
]:
|
||||||
|
return {"q": f"{city},{second.upper()}"}
|
||||||
|
else:
|
||||||
|
return {"q": f"{city},{second},US"}
|
||||||
|
|
||||||
|
elif len(params) == 3:
|
||||||
|
city, state, country = params
|
||||||
|
return {"q": f"{city},{state},{country.upper()}"}
|
||||||
|
|
||||||
|
else:
|
||||||
|
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(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(params)
|
||||||
|
return {"q": f"{city_name},US"}
|
||||||
|
|
||||||
|
async def get_weather(self, ctx, location, units="metric"):
|
||||||
|
location_params = self.parse_location(location)
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
'zip': f'{zip_code},{country_code}',
|
**location_params,
|
||||||
'appid': self.api_key,
|
"appid": self.api_key,
|
||||||
'units': units
|
"units": units,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = requests.get(self.base_url, params=params)
|
async with aiohttp.ClientSession() as session:
|
||||||
response.raise_for_status()
|
async with session.get(self.base_url, params=params) as response:
|
||||||
return response.json()
|
response.raise_for_status()
|
||||||
|
return await response.json()
|
||||||
except requests.exceptions.RequestException as e:
|
except aiohttp.ClientError as e:
|
||||||
print(f"Error fetching weather data: {e}")
|
logger.error(f"Error fetching weather data for '{location}': {e}")
|
||||||
|
await ctx.send(f"`Error fetching weather data for '{location}': {e}`")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def weather_embed(self, weather_data):
|
def weather_embed(self, weather_data):
|
||||||
if not weather_data:
|
if not weather_data:
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="❌ Error",
|
||||||
|
description="Could not fetch weather data",
|
||||||
|
color=discord.Color.red(),
|
||||||
|
)
|
||||||
|
return embed
|
||||||
|
|
||||||
|
weather_emojis = {
|
||||||
|
"clear sky": "☀️",
|
||||||
|
"few clouds": "🌤️",
|
||||||
|
"scattered clouds": "⛅",
|
||||||
|
"broken clouds": "☁️",
|
||||||
|
"shower rain": "🌦️",
|
||||||
|
"rain": "🌧️",
|
||||||
|
"thunderstorm": "⛈️",
|
||||||
|
"snow": "❄️",
|
||||||
|
"mist": "🌫️",
|
||||||
|
}
|
||||||
|
|
||||||
|
condition = weather_data["weather"][0]["description"].lower()
|
||||||
|
emoji = weather_emojis.get(condition, "🌍")
|
||||||
|
|
||||||
embed = discord.Embed(
|
embed = discord.Embed(
|
||||||
title="❌ Error",
|
title=f"{emoji} Weather in {weather_data['name']}",
|
||||||
description="Could not fetch weather data",
|
description=f"{weather_data['weather'][0]['description'].title()}",
|
||||||
color=discord.Color.red()
|
color=discord.Color.blue(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="🌡️ Temperature",
|
||||||
|
value=f"{weather_data['main']['temp']}°C\nFeels like {weather_data['main']['feels_like']}°C",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="💧 Humidity",
|
||||||
|
value=f"{weather_data['main']['humidity']}%",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="🗜️ Pressure",
|
||||||
|
value=f"{weather_data['main']['pressure']} hPa",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if "wind" in weather_data:
|
||||||
|
embed.add_field(
|
||||||
|
name="💨 Wind Speed",
|
||||||
|
value=f"{weather_data['wind']['speed']} m/s",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
if "visibility" in weather_data:
|
||||||
|
embed.add_field(
|
||||||
|
name="👁️ Visibility",
|
||||||
|
value=f"{weather_data['visibility'] / 1000} km",
|
||||||
|
inline=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.set_footer(
|
||||||
|
text=f"Lat: {weather_data['coord']['lat']}, Lon: {weather_data['coord']['lon']}"
|
||||||
|
)
|
||||||
|
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
weather_emojis = {
|
|
||||||
'clear sky': '☀️',
|
|
||||||
'few clouds': '🌤️',
|
|
||||||
'scattered clouds': '⛅',
|
|
||||||
'broken clouds': '☁️',
|
|
||||||
'shower rain': '🌦️',
|
|
||||||
'rain': '🌧️',
|
|
||||||
'thunderstorm': '⛈️',
|
|
||||||
'snow': '❄️',
|
|
||||||
'mist': '🌫️'
|
|
||||||
}
|
|
||||||
|
|
||||||
condition = weather_data['weather'][0]['description'].lower()
|
|
||||||
emoji = weather_emojis.get(condition, '🌍')
|
|
||||||
|
|
||||||
embed = discord.Embed(
|
|
||||||
title=f"{emoji} Weather in {weather_data['name']}",
|
|
||||||
description=f"{weather_data['weather'][0]['description'].title()}",
|
|
||||||
color=discord.Color.blue()
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.add_field(
|
|
||||||
name="🌡️ Temperature",
|
|
||||||
value=f"{weather_data['main']['temp']}°C\nFeels like {weather_data['main']['feels_like']}°C",
|
|
||||||
inline=True
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.add_field(
|
|
||||||
name="💧 Humidity",
|
|
||||||
value=f"{weather_data['main']['humidity']}%",
|
|
||||||
inline=True
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.add_field(
|
|
||||||
name="🗜️ Pressure",
|
|
||||||
value=f"{weather_data['main']['pressure']} hPa",
|
|
||||||
inline=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'wind' in weather_data:
|
|
||||||
embed.add_field(
|
|
||||||
name="💨 Wind Speed",
|
|
||||||
value=f"{weather_data['wind']['speed']} m/s",
|
|
||||||
inline=True
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'visibility' in weather_data:
|
|
||||||
embed.add_field(
|
|
||||||
name="👁️ Visibility",
|
|
||||||
value=f"{weather_data['visibility']/1000} km",
|
|
||||||
inline=True
|
|
||||||
)
|
|
||||||
|
|
||||||
embed.set_footer(
|
|
||||||
text=f"Lat: {weather_data['coord']['lat']}, Lon: {weather_data['coord']['lon']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return embed
|
|
||||||
|
|
||||||
@commands.command(name='weather')
|
async def weather(self, ctx, location):
|
||||||
async def weather_command(self, ctx, zip_code: str, country_code: str = 'US'):
|
weather_data = await self.get_weather(ctx, location)
|
||||||
"""
|
if weather_data:
|
||||||
Get weather by zip code
|
embed = self.weather_embed(weather_data)
|
||||||
Usage: !weather 10001 US
|
await ctx.send(embed=embed)
|
||||||
"""
|
|
||||||
await ctx.typing()
|
|
||||||
|
|
||||||
weather_data = await self.get_weather_by_zip(zip_code, country_code)
|
|
||||||
embed = self.create_weather_embed(weather_data)
|
|
||||||
|
|
||||||
await ctx.send(embed=embed)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# def display_weather(self, weather_data):
|
|
||||||
# """Pretty print weather information"""
|
|
||||||
# if not weather_data:
|
|
||||||
# print("No weather data available")
|
|
||||||
# return
|
|
||||||
|
|
||||||
# print(f"\n🌤️ Weather for {weather_data['name']}")
|
|
||||||
# print(f"Temperature: {weather_data['main']['temp']}°C")
|
|
||||||
# print(f"Feels like: {weather_data['main']['feels_like']}°C")
|
|
||||||
# print(f"Condition: {weather_data['weather'][0]['description'].title()}")
|
|
||||||
# print(f"Humidity: {weather_data['main']['humidity']}%")
|
|
||||||
# print(f"Pressure: {weather_data['main']['pressure']} hPa")
|
|
||||||
# if 'visibility' in weather_data:
|
|
||||||
# print(f"Visibility: {weather_data['visibility']/1000} km")
|
|
||||||
|
|
||||||
# if __name__ == "__main__":
|
|
||||||
# API_KEY = "x"
|
|
||||||
|
|
||||||
# weather = WeatherAPI(API_KEY)
|
|
||||||
|
|
||||||
# zip_codes = ['10001', '90210', '60601']
|
|
||||||
|
|
||||||
# for zip_code in zip_codes:
|
|
||||||
# data = weather.get_weather_by_zip(zip_code)
|
|
||||||
# weather.display_weather(data)
|
|
||||||
# print("-" * 40)
|
|
||||||
|
Reference in New Issue
Block a user