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 aiohttp
|
||||
RUN pip3 install requests
|
||||
RUN pip3 install wikipedia
|
||||
RUN pip3 install pillow
|
||||
RUN pip3 install qrcode
|
||||
|
||||
CMD [ "python", "garfmain.py" ]
|
||||
|
@ -16,7 +16,7 @@ Responds with iputils-ping result from target.
|
||||
`garfpic {target}`
|
||||
Responds with dns lookup result from target.
|
||||
|
||||
`garfpic {target}`
|
||||
`garfhack {target}`
|
||||
Responds with nmap scan result from target.
|
||||
|
||||
`garfshop {item} {zip}`
|
||||
@ -36,7 +36,6 @@ Add your various API tokens:
|
||||
```python
|
||||
GARFBOT_TOKEN = "Discord 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:
|
||||
|
53
garfmain.py
53
garfmain.py
@ -1,5 +1,4 @@
|
||||
import config
|
||||
# import random
|
||||
import asyncio
|
||||
import discord
|
||||
import subprocess
|
||||
@ -7,8 +6,8 @@ import subprocess
|
||||
from garfpy import(
|
||||
logger, is_private,
|
||||
kroger_token, find_store, search_product,
|
||||
picture_time, process_image_requests, generate_chat,
|
||||
aod_message)
|
||||
garfpic, process_image_requests, generate_chat,
|
||||
aod_message, wikisum, generate_qr)
|
||||
|
||||
|
||||
gapikey = config.GIF_TOKEN
|
||||
@ -38,8 +37,11 @@ async def on_message(message):
|
||||
return
|
||||
|
||||
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
|
||||
answer = await generate_chat(question)
|
||||
logger.info(f"Chat Request - User: {user}, Server: {server}, Prompt: {question}")
|
||||
await message.channel.send(answer)
|
||||
|
||||
if message.content.lower().startswith('garfpic '):
|
||||
@ -48,17 +50,25 @@ async def on_message(message):
|
||||
prompt = message.content[8:]
|
||||
logger.info(f"Image Request - User: {user}, Server: {server}, Prompt: {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":
|
||||
# await send_gif(message, "garfield lasagna")
|
||||
if message.content.lower().startswith('garfwiki '):
|
||||
search_term = message.content[9:]
|
||||
summary = await wikisum(search_term)
|
||||
await message.channel.send(summary)
|
||||
|
||||
# if message.content.lower() == "monday":
|
||||
# await send_gif(message, "garfield monday")
|
||||
|
||||
# if message.content.lower().startswith("garfgif "):
|
||||
# search_term = message.content[8:]
|
||||
# await send_gif(message, search_term)
|
||||
if message.content.lower().startswith('garfqr '):
|
||||
text = message.content[7:]
|
||||
if len(text) > 1000:
|
||||
await mesage.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)
|
||||
|
||||
if message.content.lower().startswith("garfping "):
|
||||
try:
|
||||
@ -137,25 +147,6 @@ async def on_message(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():
|
||||
while True:
|
||||
try:
|
||||
|
@ -4,9 +4,11 @@ from .kroger import(
|
||||
kroger_token, find_store, search_product
|
||||
)
|
||||
from .garfai import(
|
||||
picture_time,
|
||||
garfpic,
|
||||
process_image_requests,
|
||||
generate_chat
|
||||
)
|
||||
from .iputils import is_private
|
||||
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
|
||||
imgmodel = config.IMG_MODEL
|
||||
|
||||
# GarfPics
|
||||
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})
|
||||
|
||||
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
|
||||
aiohttp
|
||||
requests
|
||||
wikipedia
|
||||
pillow
|
||||
qrcode
|
Reference in New Issue
Block a user