Compare commits
24 Commits
v1.0
...
4b077e3fd9
Author | SHA1 | Date | |
---|---|---|---|
4b077e3fd9 | |||
9d5765c492 | |||
8216af5f2a | |||
219b1745da | |||
d570069e6b | |||
f267526411 | |||
86fa47d259 | |||
888e647e1f | |||
c1beb8374e | |||
48a7033ba6 | |||
9e436d8d78 | |||
d8c286bb2d | |||
a081edfa97 | |||
716e30e2b1 | |||
73499a9586 | |||
e3a762fe10 | |||
842b592bfc | |||
f9f88be8af | |||
5c8696216e | |||
aa16064d17 | |||
ffd642fd2a | |||
3945eb6763 | |||
0438b55ce6 | |||
8fb4e089be |
28
.gitea/workflows/deploy.yaml
Normal file
28
.gitea/workflows/deploy.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
name: Garfbot CI/CD Deployment
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Deploy:
|
||||||
|
container:
|
||||||
|
volumes:
|
||||||
|
- /home/crate/garfbot:/workspace/crate/garfbot/deploy
|
||||||
|
steps:
|
||||||
|
- name: Pull Garfbot and restart container
|
||||||
|
run: |
|
||||||
|
cd /workspace/crate/garfbot/deploy
|
||||||
|
|
||||||
|
git pull origin main
|
||||||
|
|
||||||
|
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 run -d --restart always -v $PWD:/usr/src/app --name garfbot git.crate.zip/crate/garfbot:latest
|
||||||
|
else
|
||||||
|
docker restart garfbot
|
||||||
|
fi
|
@ -12,5 +12,8 @@ RUN pip3 install discord
|
|||||||
RUN pip3 install openai
|
RUN pip3 install openai
|
||||||
RUN pip3 install aiohttp
|
RUN pip3 install aiohttp
|
||||||
RUN pip3 install requests
|
RUN pip3 install requests
|
||||||
|
RUN pip3 install wikipedia
|
||||||
|
RUN pip3 install pillow
|
||||||
|
RUN pip3 install qrcode
|
||||||
|
|
||||||
CMD [ "python", "garfmain.py" ]
|
CMD [ "python", "garfmain.py" ]
|
||||||
|
@ -16,7 +16,7 @@ Responds with iputils-ping result from target.
|
|||||||
`garfpic {target}`
|
`garfpic {target}`
|
||||||
Responds with dns lookup result from target.
|
Responds with dns lookup result from target.
|
||||||
|
|
||||||
`garfpic {target}`
|
`garfhack {target}`
|
||||||
Responds with nmap scan result from target.
|
Responds with nmap scan result from target.
|
||||||
|
|
||||||
`garfshop {item} {zip}`
|
`garfshop {item} {zip}`
|
||||||
@ -36,7 +36,6 @@ Add your various API tokens:
|
|||||||
```python
|
```python
|
||||||
GARFBOT_TOKEN = "Discord API token"
|
GARFBOT_TOKEN = "Discord API token"
|
||||||
OPENAI_TOKEN = "OpenAI API token"
|
OPENAI_TOKEN = "OpenAI API token"
|
||||||
GIF_TOKEN = "tenor.com 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:
|
||||||
|
53
garfmain.py
53
garfmain.py
@ -1,5 +1,4 @@
|
|||||||
import config
|
import config
|
||||||
# import random
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import discord
|
import discord
|
||||||
import subprocess
|
import subprocess
|
||||||
@ -7,8 +6,8 @@ import subprocess
|
|||||||
from garfpy import(
|
from garfpy import(
|
||||||
logger, is_private,
|
logger, is_private,
|
||||||
kroger_token, find_store, search_product,
|
kroger_token, find_store, search_product,
|
||||||
picture_time, process_image_requests, generate_chat,
|
garfpic, process_image_requests, generate_chat,
|
||||||
aod_message)
|
aod_message, wikisum, generate_qr)
|
||||||
|
|
||||||
|
|
||||||
gapikey = config.GIF_TOKEN
|
gapikey = config.GIF_TOKEN
|
||||||
@ -38,8 +37,11 @@ async def on_message(message):
|
|||||||
return
|
return
|
||||||
|
|
||||||
if message.content.lower().startswith("hey garfield") or isinstance(message.channel, discord.DMChannel):
|
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
|
question = message.content[12:] if message.content.lower().startswith("hey garfield") else message.content
|
||||||
answer = await generate_chat(question)
|
answer = await generate_chat(question)
|
||||||
|
logger.info(f"Chat Request - User: {user}, Server: {server}, Prompt: {question}")
|
||||||
await message.channel.send(answer)
|
await message.channel.send(answer)
|
||||||
|
|
||||||
if message.content.lower().startswith('garfpic '):
|
if message.content.lower().startswith('garfpic '):
|
||||||
@ -48,17 +50,25 @@ async def on_message(message):
|
|||||||
prompt = message.content[8:]
|
prompt = message.content[8:]
|
||||||
logger.info(f"Image Request - User: {user}, Server: {server}, Prompt: {prompt}")
|
logger.info(f"Image Request - User: {user}, Server: {server}, Prompt: {prompt}")
|
||||||
await message.channel.send(f"`Please wait... image generation queued: {prompt}`")
|
await message.channel.send(f"`Please wait... image generation queued: {prompt}`")
|
||||||
await picture_time(message, prompt)
|
await garfpic(message, prompt)
|
||||||
|
|
||||||
# if message.content.lower() == "lasagna":
|
if message.content.lower().startswith('garfwiki '):
|
||||||
# await send_gif(message, "garfield lasagna")
|
search_term = message.content[9:]
|
||||||
|
summary = await wikisum(search_term)
|
||||||
|
await message.channel.send(summary)
|
||||||
|
|
||||||
# if message.content.lower() == "monday":
|
if message.content.lower().startswith('garfqr '):
|
||||||
# await send_gif(message, "garfield monday")
|
text = message.content[7:]
|
||||||
|
if len(text) > 1000:
|
||||||
# if message.content.lower().startswith("garfgif "):
|
await mesage.channel.send("❌ Text too long! Maximum 1000 characters.")
|
||||||
# search_term = message.content[8:]
|
else:
|
||||||
# await send_gif(message, search_term)
|
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)
|
||||||
|
|
||||||
if message.content.lower().startswith("garfping "):
|
if message.content.lower().startswith("garfping "):
|
||||||
try:
|
try:
|
||||||
@ -137,25 +147,6 @@ async def on_message(message):
|
|||||||
await aod_message(garfbot, message)
|
await aod_message(garfbot, message)
|
||||||
|
|
||||||
|
|
||||||
# # GarfGifs
|
|
||||||
# @garfbot.event
|
|
||||||
# async def send_gif(message, search_term):
|
|
||||||
# lmt = 50
|
|
||||||
# ckey = "garfbot"
|
|
||||||
# r = requests.get(f"https://tenor.googleapis.com/v2/search?q={search_term}&key={gapikey}&client_key={ckey}&limit={lmt}")
|
|
||||||
# if r.status_code == 200:
|
|
||||||
# top_50gifs = json.loads(r.content)
|
|
||||||
# gif_url = random.choice(top_50gifs["results"])["itemurl"]
|
|
||||||
# logger.info(gif_url)
|
|
||||||
# # logger.info(gif_url)
|
|
||||||
# try:
|
|
||||||
# await message.channel.send(gif_url)
|
|
||||||
# except KeyError:
|
|
||||||
# await message.channel.send("Oops, something went wrong.")
|
|
||||||
# else:
|
|
||||||
# await message.channel.send(f"`Oops, something went wrong. Error code: {r.status_code}`")
|
|
||||||
|
|
||||||
|
|
||||||
async def garfbot_connect():
|
async def garfbot_connect():
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -4,9 +4,11 @@ from .kroger import(
|
|||||||
kroger_token, find_store, search_product
|
kroger_token, find_store, search_product
|
||||||
)
|
)
|
||||||
from .garfai import(
|
from .garfai import(
|
||||||
picture_time,
|
garfpic,
|
||||||
process_image_requests,
|
process_image_requests,
|
||||||
generate_chat
|
generate_chat
|
||||||
)
|
)
|
||||||
from .iputils import is_private
|
from .iputils import is_private
|
||||||
from .aod import aod_message
|
from .aod import aod_message
|
||||||
|
from .wiki import wikisum
|
||||||
|
from .qr import generate_qr
|
@ -12,10 +12,9 @@ openaikey = config.OPENAI_TOKEN
|
|||||||
txtmodel = config.TXT_MODEL
|
txtmodel = config.TXT_MODEL
|
||||||
imgmodel = config.IMG_MODEL
|
imgmodel = config.IMG_MODEL
|
||||||
|
|
||||||
# GarfPics
|
|
||||||
image_request_queue = asyncio.Queue()
|
image_request_queue = asyncio.Queue()
|
||||||
|
|
||||||
async def picture_time(message, prompt):
|
async def garfpic(message, prompt):
|
||||||
await image_request_queue.put({'message': message, 'prompt': prompt})
|
await image_request_queue.put({'message': message, 'prompt': prompt})
|
||||||
|
|
||||||
async def generate_image(prompt):
|
async def generate_image(prompt):
|
||||||
|
63
garfpy/qr.py
Normal file
63
garfpy/qr.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import qrcode
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_qr_settings(text):
|
||||||
|
text_length = len(text)
|
||||||
|
|
||||||
|
if text_length <= 25:
|
||||||
|
version = 1
|
||||||
|
box_size = 12
|
||||||
|
elif text_length <= 47:
|
||||||
|
version = 2
|
||||||
|
box_size = 10
|
||||||
|
elif text_length <= 77:
|
||||||
|
version = 3
|
||||||
|
box_size = 8
|
||||||
|
elif text_length <= 114:
|
||||||
|
version = 4
|
||||||
|
box_size = 7
|
||||||
|
elif text_length <= 154:
|
||||||
|
version = 5
|
||||||
|
box_size = 6
|
||||||
|
elif text_length <= 195:
|
||||||
|
version = 6
|
||||||
|
box_size = 5
|
||||||
|
elif text_length <= 224:
|
||||||
|
version = 7
|
||||||
|
box_size = 5
|
||||||
|
elif text_length <= 279:
|
||||||
|
version = 8
|
||||||
|
box_size = 4
|
||||||
|
elif text_length <= 335:
|
||||||
|
version = 9
|
||||||
|
box_size = 4
|
||||||
|
elif text_length <= 395:
|
||||||
|
version = 10
|
||||||
|
box_size = 3
|
||||||
|
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')
|
||||||
|
img_buffer.seek(0)
|
||||||
|
|
||||||
|
return img_buffer
|
142
garfpy/weather.py
Normal file
142
garfpy/weather.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import discord
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
class WeatherAPI:
|
||||||
|
def __init__(self, api_key):
|
||||||
|
self.api_key = api_key
|
||||||
|
self.base_url = "https://api.openweathermap.org/data/2.5/weather"
|
||||||
|
|
||||||
|
def get_weather_by_zip(self, zip_code, country_code='US', units='metric'):
|
||||||
|
"""
|
||||||
|
Get weather data by zip code
|
||||||
|
|
||||||
|
Args:
|
||||||
|
zip_code (str): ZIP code
|
||||||
|
country_code (str): Country code (default: 'US')
|
||||||
|
units (str): 'metric', 'imperial', or 'standard'
|
||||||
|
"""
|
||||||
|
params = {
|
||||||
|
'zip': f'{zip_code},{country_code}',
|
||||||
|
'appid': self.api_key,
|
||||||
|
'units': units
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(self.base_url, params=params)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error fetching weather data: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def weather_embed(self, 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(
|
||||||
|
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_command(self, ctx, zip_code: str, country_code: str = 'US'):
|
||||||
|
"""
|
||||||
|
Get weather by zip code
|
||||||
|
Usage: !weather 10001 US
|
||||||
|
"""
|
||||||
|
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)
|
12
garfpy/wiki.py
Normal file
12
garfpy/wiki.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import wikipedia
|
||||||
|
from garfpy import generate_chat
|
||||||
|
|
||||||
|
async def wikisum(search_term):
|
||||||
|
try:
|
||||||
|
summary = wikipedia.summary(search_term)
|
||||||
|
garfsum = await generate_chat(f"Please summarize in your own words: {summary}")
|
||||||
|
|
||||||
|
return garfsum
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return e
|
@ -2,3 +2,6 @@ discord.py
|
|||||||
openai
|
openai
|
||||||
aiohttp
|
aiohttp
|
||||||
requests
|
requests
|
||||||
|
wikipedia
|
||||||
|
pillow
|
||||||
|
qrcode
|
Reference in New Issue
Block a user