1 Commits

Author SHA1 Message Date
4ddcd2b374 env vars testing 2026-01-04 05:36:35 -06:00
10 changed files with 46 additions and 109 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@ config.py
__pycache__/ __pycache__/
garfpy/__pycache__/ garfpy/__pycache__/
*.venv* *.venv*
.idea
*.json *.json
*.old *.old
*.log* *.log*

View File

@@ -18,7 +18,6 @@ RUN apk update && \
tcl-dev tcl-dev
RUN pip3 install --no-cache-dir \ RUN pip3 install --no-cache-dir \
config \
discord \ discord \
openai \ openai \
aiohttp \ aiohttp \

View File

@@ -1,6 +1,6 @@
MIT No Attribution MIT No Attribution
Copyright 2026 Aaron Crate Copyright 2025 Aaron Crate
Permission is hereby granted, free of charge, to any person obtaining a copy of this Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software software and associated documentation files (the "Software"), to deal in the Software

View File

@@ -2,6 +2,14 @@ services:
garfbot: garfbot:
image: git.crate.zip/crate/garfbot:latest image: git.crate.zip/crate/garfbot:latest
container_name: garfbot container_name: garfbot
environment:
- TXT_MODEL=${TXT_MODEL}
- IMG_MODEL=${IMG_MODEL}
- GARFBOT_TOKEN=${GARFBOT_TOKEN}
- OPENAI_TOKEN=${OPENAI_TOKEN}
- WEATHER_TOKEN=${WEATHER_TOKEN}
- KROGER_ID=${KROGER_ID}
- KROGER_SECRET=${KROGER_SECRET}
restart: always restart: always
volumes: volumes:
- /home/crate/garfbot:/usr/src/app - /home/crate/garfbot:/usr/src/app

View File

@@ -1,9 +1,7 @@
import re import os
import config
import asyncio import asyncio
import discord import discord
from discord.ext import commands from discord.ext import commands
from urllib.parse import urlparse, parse_qs
from garfpy import ( from garfpy import (
help, help,
@@ -18,10 +16,9 @@ from garfpy import (
) )
# gapikey = config.GIF_TOKEN garfkey = os.getenv("GARFBOT_TOKEN")
garfkey = config.GARFBOT_TOKEN txtmodel = os.getenv("TXT_MODEL")
txtmodel = config.TXT_MODEL imgmodel = os.getenv("IMG_MODEL")
imgmodel = config.IMG_MODEL
intents = discord.Intents.default() intents = discord.Intents.default()
intents.members = True intents.members = True
@@ -42,36 +39,6 @@ kroger = Kroger()
weather = WeatherAPI() weather = WeatherAPI()
URL_PATTERNS = [
r'https?://(?:www\.)?youtube\.com/watch\?[^\s]*',
r'https?://youtu\.be/[^\s]*',
r'https?://(?:open\.)?spotify\.com/[^\s]*',
]
def clean_url(url):
try:
parsed = urlparse(url)
if 'youtube.com' in parsed.hostname:
params = parse_qs(parsed.query)
video_id = params.get('v', [None])[0]
if not video_id:
return None
# timestamp = params.get('t', [None])[0]
# if timestamp:
# return f"https://www.youtube.com/watch?v={video_id}&t={timestamp}"
return f"https://www.youtube.com/watch?v={video_id}"
if 'youtu.be' in parsed.hostname:
return f"https://youtu.be{parsed.path}"
if 'spotify.com' in parsed.hostname:
return f"https://open.spotify.com{parsed.path}"
except Exception:
return None
@garfbot.event @garfbot.event
async def on_ready(): async def on_ready():
try: try:
@@ -117,30 +84,30 @@ async def garfbot_qr(ctx, *, text):
f"QR Code Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Text: {text}" f"QR Code Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Text: {text}"
) )
if len(text) > 1000: if len(text) > 1000:
await ctx.reply("❌ Text too long! Maximum 1000 characters.") await ctx.send("❌ Text too long! Maximum 1000 characters.")
else: else:
try: try:
qr_code = await generate_qr(text) qr_code = await generate_qr(text)
sendfile = discord.File(fp=qr_code, filename="qrcode.png") sendfile = discord.File(fp=qr_code, filename="qrcode.png")
await ctx.reply(file=sendfile) await ctx.send(file=sendfile)
except Exception as e: except Exception as e:
logger.error(e) logger.error(e)
await ctx.reply(e) await ctx.send(e)
@garfbot.command(name="wiki") @garfbot.command(name="wiki")
async def garfbot_wiki(ctx, *, query): async def garfbot_wiki(ctx, *, query):
summary = await garfield.wikisum(query) summary = await garfield.wikisum(query)
await ctx.reply(summary) await ctx.send(summary)
@garfbot.command(name="shop") @garfbot.command(name="shop")
async def garfbot_shop(ctx, *, query): async def garfbot_shop(ctx, *, query):
try: try:
response = kroger.garfshop(query) response = kroger.garfshop(query)
await ctx.reply(response) await ctx.send(response)
except Exception as e: except Exception as e:
await ctx.reply(f"`GarfBot Error: {str(e)}`") await ctx.send(f"`GarfBot Error: {str(e)}`")
@garfbot.command(name="weather") @garfbot.command(name="weather")
@@ -150,24 +117,20 @@ async def garfbot_weather(ctx, *, location):
@garfbot.command(name="chat") @garfbot.command(name="chat")
async def garfchat(ctx, *, prompt): async def garfchat(ctx, *, prompt):
if "is this true" in prompt.lower():
messages = [msg async for msg in ctx.channel.history(limit=2)]
prompt = messages[1].content
prompt = f"Is this true: {prompt}"
answer = await garfield.generate_chat(prompt) answer = await garfield.generate_chat(prompt)
logger.info( logger.info(
f"Chat Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}" f"Chat Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
) )
await ctx.reply(answer) await ctx.send(answer)
# @garfbot.command(name="pic") @garfbot.command(name="pic")
# async def garfpic(ctx, *, prompt): async def garfpic(ctx, *, prompt):
# logger.info( logger.info(
# f"Image Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}" f"Image Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
# ) )
# await ctx.reply(f"`Please wait... image generation queued: {prompt}`") await ctx.send(f"`Please wait... image generation queued: {prompt}`")
# await garfield.garfpic(ctx, prompt) await garfield.garfpic(ctx, prompt)
@garfbot.command(name="help") @garfbot.command(name="help")
@@ -184,19 +147,6 @@ async def on_message(message):
content = message.content.strip() content = message.content.strip()
lower = content.lower() lower = content.lower()
# # Remove tracking stuff from youtube and spotify links
# cleaned_urls = []
# for pattern in URL_PATTERNS:
# for match in re.finditer(pattern, message.content):
# cleaned = clean_url(match.group(0))
# if cleaned and cleaned != match.group(0):
# cleaned_urls.append(cleaned)
# if cleaned_urls:
# links = '\n'.join(cleaned_urls)
# await message.reply(f"🔗 Cleaned link{'s' if len(cleaned_urls) > 1 else ''}:\n{links}")
# Chats & pics # Chats & pics
if lower.startswith("hey garfield") or isinstance( if lower.startswith("hey garfield") or isinstance(
message.channel, discord.DMChannel message.channel, discord.DMChannel
@@ -230,7 +180,7 @@ async def on_message(message):
async def garfbot_connect(): async def garfbot_connect():
while True: while True:
try: try:
await garfbot.start(garfkey) await garfbot.start(garfkey) # type: ignore
except Exception as e: except Exception as e:
e = str(e) e = str(e)
logger.error(f"Garfbot couldn't connect! {e}") logger.error(f"Garfbot couldn't connect! {e}")

View File

@@ -1,5 +1,4 @@
import os import os
import re
import json import json
import discord import discord
from garfpy import logger from garfpy import logger
@@ -122,17 +121,3 @@ async def aod_message(garfbot, message):
for field, values in zip(table_fields, table_columns): for field, values in zip(table_fields, table_columns):
stats_embed.add_field(name=field, value="\n".join(values), inline=True) stats_embed.add_field(name=field, value="\n".join(values), inline=True)
await message.channel.send(embed=stats_embed) await message.channel.send(embed=stats_embed)
# # Boy You Said It
# words = re.findall(r"[a-zA-Z']+", message.content.lower())
# stops = {"a", "an", "the", "and", "or", "but", "is", "it", "in", "on", "at", "to", "of"}
# words = [w for w in words if w not in stops]
# if words:
# firsts = [w[0] for w in words]
# commons = max(set(firsts), key=firsts.count)
# count = firsts.count(commons)
# if count >= 3 or (len(words) >= 2 and count / len(words) >= 0.75):
# await message.channel.send("Boy, you said it!")

View File

@@ -1,6 +1,6 @@
import io import io
import os
import openai import openai
import config
import aiohttp import aiohttp
import asyncio import asyncio
import discord import discord
@@ -11,11 +11,9 @@ from garfpy import logger
class GarfAI: class GarfAI:
def __init__(self): def __init__(self):
self.baseurl = config.BASE_URL self.openaikey = os.getenv('OPENAI_TOKEN')
self.openaikey = config.OPENAI_TOKEN self.txtmodel = os.getenv('TXT_MODEL')
self.sysprompt = config.SYSTEM_PROMPT self.imgmodel = os.getenv('IMG_MODEL')
self.txtmodel = config.TXT_MODEL
self.imgmodel = config.IMG_MODEL
self.image_request_queue = asyncio.Queue() self.image_request_queue = asyncio.Queue()
async def garfpic(self, ctx, prompt): async def garfpic(self, ctx, prompt):
@@ -82,21 +80,17 @@ class GarfAI:
async def generate_chat(self, question): async def generate_chat(self, question):
try: try:
client = AsyncOpenAI( client = AsyncOpenAI(api_key=self.openaikey)
api_key=self.openaikey,
base_url=self.baseurl
)
response = await client.chat.completions.create( response = await client.chat.completions.create(
model=self.txtmodel, model=self.txtmodel, # type: ignore
messages=[ messages=[
{ {
"role": "system", "role": "system",
"content": self.sysprompt, "content": "Pretend you are sarcastic Garfield.",
}, },
{"role": "user", "content": question}, {"role": "user", "content": f"{question}"},
], ],
max_tokens=400, max_tokens=400,
temperature=1.2,
) )
answer = str(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")

View File

@@ -6,9 +6,9 @@ async def help(message):
embed.add_field( embed.add_field(
name="hey garfield `prompt`", value="*Responds with text.*", inline=True name="hey garfield `prompt`", value="*Responds with text.*", inline=True
) )
# embed.add_field( embed.add_field(
# name="garfpic `prompt`", value="*Responds with an image.*", inline=True name="garfpic `prompt`", value="*Responds with an image.*", inline=True
# ) )
embed.add_field( embed.add_field(
name="garfping `target`", name="garfping `target`",
value="*Responds with iputils-ping result from target.*", value="*Responds with iputils-ping result from target.*",

View File

@@ -1,4 +1,4 @@
import config import os
import requests import requests
from base64 import b64encode from base64 import b64encode
from garfpy import logger from garfpy import logger
@@ -6,8 +6,8 @@ from garfpy import logger
class Kroger: class Kroger:
def __init__(self): def __init__(self):
self.client_id = config.CLIENT_ID self.client_id = os.getenv('CLIENT_ID')
self.client_secret = config.CLIENT_SECRET self.client_secret = os.getenv('CLIENT_SECRET')
self.auth = b64encode( self.auth = b64encode(
f"{self.client_id}:{self.client_secret}".encode() f"{self.client_id}:{self.client_secret}".encode()
).decode() ).decode()

View File

@@ -1,5 +1,5 @@
import os
import re import re
import config
import discord import discord
import aiohttp import aiohttp
from garfpy import logger from garfpy import logger
@@ -7,7 +7,7 @@ from garfpy import logger
class WeatherAPI: class WeatherAPI:
def __init__(self, api_key=None): def __init__(self, api_key=None):
self.api_key = api_key or config.WEATHER_TOKEN self.api_key = api_key or os.getenv("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 parse_location(self, location): def parse_location(self, location):
@@ -126,7 +126,7 @@ class WeatherAPI:
response.raise_for_status() response.raise_for_status()
return await response.json() return await response.json()
except aiohttp.ClientError as e: except aiohttp.ClientError as e:
logger.error(f"Error fetching weather data for '{location}' - {e}") logger.error(f"Error fetching weather data for '{location}'")
await ctx.send(f"`Error fetching weather data for '{location}'`") await ctx.send(f"`Error fetching weather data for '{location}'`")
return None return None