Compare commits
10 Commits
env-vars
...
3afb2e5132
| Author | SHA1 | Date | |
|---|---|---|---|
| 3afb2e5132 | |||
| 0ef3fd1e82 | |||
| f81a76e505 | |||
| 8212edc406 | |||
| 386a7e764d | |||
| 4880229e26 | |||
| bfef23eb72 | |||
| 940b6b80d9 | |||
| f77940dff9 | |||
| 68696e25f0 |
@@ -18,6 +18,7 @@ RUN apk update && \
|
||||
tcl-dev
|
||||
|
||||
RUN pip3 install --no-cache-dir \
|
||||
config \
|
||||
discord \
|
||||
openai \
|
||||
aiohttp \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT No Attribution
|
||||
|
||||
Copyright 2025 Aaron Crate
|
||||
Copyright 2026 Aaron Crate
|
||||
|
||||
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
|
||||
|
||||
79
garfmain.py
79
garfmain.py
@@ -1,7 +1,9 @@
|
||||
import re
|
||||
import config
|
||||
import asyncio
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
from garfpy import (
|
||||
help,
|
||||
@@ -16,7 +18,7 @@ from garfpy import (
|
||||
)
|
||||
|
||||
|
||||
gapikey = config.GIF_TOKEN
|
||||
# gapikey = config.GIF_TOKEN
|
||||
garfkey = config.GARFBOT_TOKEN
|
||||
txtmodel = config.TXT_MODEL
|
||||
imgmodel = config.IMG_MODEL
|
||||
@@ -40,6 +42,36 @@ kroger = Kroger()
|
||||
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
|
||||
async def on_ready():
|
||||
try:
|
||||
@@ -85,30 +117,30 @@ async def garfbot_qr(ctx, *, text):
|
||||
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.")
|
||||
await ctx.reply("❌ 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)
|
||||
await ctx.reply(file=sendfile)
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
await ctx.send(e)
|
||||
await ctx.reply(e)
|
||||
|
||||
|
||||
@garfbot.command(name="wiki")
|
||||
async def garfbot_wiki(ctx, *, query):
|
||||
summary = await garfield.wikisum(query)
|
||||
await ctx.send(summary)
|
||||
await ctx.reply(summary)
|
||||
|
||||
|
||||
@garfbot.command(name="shop")
|
||||
async def garfbot_shop(ctx, *, query):
|
||||
try:
|
||||
response = kroger.garfshop(query)
|
||||
await ctx.send(response)
|
||||
await ctx.reply(response)
|
||||
except Exception as e:
|
||||
await ctx.send(f"`GarfBot Error: {str(e)}`")
|
||||
await ctx.reply(f"`GarfBot Error: {str(e)}`")
|
||||
|
||||
|
||||
@garfbot.command(name="weather")
|
||||
@@ -118,20 +150,24 @@ async def garfbot_weather(ctx, *, location):
|
||||
|
||||
@garfbot.command(name="chat")
|
||||
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)
|
||||
logger.info(
|
||||
f"Chat Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
|
||||
)
|
||||
await ctx.send(answer)
|
||||
await ctx.reply(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="pic")
|
||||
# async def garfpic(ctx, *, prompt):
|
||||
# logger.info(
|
||||
# f"Image Request - User: {ctx.author.name}, Server: {ctx.guild.name}, Prompt: {prompt}"
|
||||
# )
|
||||
# await ctx.reply(f"`Please wait... image generation queued: {prompt}`")
|
||||
# await garfield.garfpic(ctx, prompt)
|
||||
|
||||
|
||||
@garfbot.command(name="help")
|
||||
@@ -148,6 +184,19 @@ async def on_message(message):
|
||||
content = message.content.strip()
|
||||
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
|
||||
if lower.startswith("hey garfield") or isinstance(
|
||||
message.channel, discord.DMChannel
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
import discord
|
||||
from garfpy import logger
|
||||
@@ -121,3 +122,17 @@ async def aod_message(garfbot, message):
|
||||
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)
|
||||
|
||||
# # 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!")
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ from garfpy import logger
|
||||
|
||||
class GarfAI:
|
||||
def __init__(self):
|
||||
self.baseurl = config.BASE_URL
|
||||
self.openaikey = config.OPENAI_TOKEN
|
||||
self.sysprompt = config.SYSTEM_PROMPT
|
||||
self.txtmodel = config.TXT_MODEL
|
||||
self.imgmodel = config.IMG_MODEL
|
||||
self.image_request_queue = asyncio.Queue()
|
||||
@@ -80,17 +82,21 @@ class GarfAI:
|
||||
|
||||
async def generate_chat(self, question):
|
||||
try:
|
||||
client = AsyncOpenAI(api_key=self.openaikey)
|
||||
client = AsyncOpenAI(
|
||||
api_key=self.openaikey,
|
||||
base_url=self.baseurl
|
||||
)
|
||||
response = await client.chat.completions.create(
|
||||
model=self.txtmodel,
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "Pretend you are sarcastic Garfield.",
|
||||
"content": self.sysprompt,
|
||||
},
|
||||
{"role": "user", "content": f"{question}"},
|
||||
{"role": "user", "content": question},
|
||||
],
|
||||
max_tokens=400,
|
||||
temperature=1.2,
|
||||
)
|
||||
answer = str(response.choices[0].message.content)
|
||||
return answer.replace("an AI language model", "a cartoon animal")
|
||||
|
||||
@@ -6,9 +6,9 @@ async def help(message):
|
||||
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="garfpic `prompt`", value="*Responds with an image.*", inline=True
|
||||
# )
|
||||
embed.add_field(
|
||||
name="garfping `target`",
|
||||
value="*Responds with iputils-ping result from target.*",
|
||||
|
||||
@@ -126,7 +126,7 @@ class WeatherAPI:
|
||||
response.raise_for_status()
|
||||
return await response.json()
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"Error fetching weather data for '{location}'")
|
||||
logger.error(f"Error fetching weather data for '{location}' - {e}")
|
||||
await ctx.send(f"`Error fetching weather data for '{location}'`")
|
||||
return None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user