Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b67240976 | ||
|
|
16a19e6913 | ||
|
|
4ae03c9b9d | ||
|
|
074ff8bbb4 | ||
|
|
4e22274f9b | ||
|
|
a5ed9f9e1c |
@@ -2,6 +2,7 @@
|
||||
cogs made for our Royal butt.
|
||||
|
||||
## Our Cogs:
|
||||
- [FitnessCog](./fitnessCog)
|
||||
- [RecruitmentCog](./recruitmentCog/)
|
||||
- [ReginaldCog](./reginaldCog/)
|
||||
- [TrafficCog](./trafficCog)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
from redbot.core.bot import Red
|
||||
from .fitness import FitnessCog
|
||||
|
||||
async def setup(bot: Red):
|
||||
cog = FitnessCog(bot)
|
||||
await bot.add_cog(cog)
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"convertable": true,
|
||||
"units": {
|
||||
"meter": {
|
||||
"factor": 1,
|
||||
"aliases": [
|
||||
"meter",
|
||||
"meters",
|
||||
"m",
|
||||
"m."
|
||||
]
|
||||
},
|
||||
"kilometer": {
|
||||
"factor": 1000,
|
||||
"aliases": [
|
||||
"kilometer",
|
||||
"kilometers",
|
||||
"km",
|
||||
"km."
|
||||
]
|
||||
},
|
||||
"foot": {
|
||||
"factor": 0.3048,
|
||||
"aliases": [
|
||||
"foot",
|
||||
"feet",
|
||||
"ft",
|
||||
"ft."
|
||||
]
|
||||
},
|
||||
"mile": {
|
||||
"factor": 1609.344,
|
||||
"aliases": [
|
||||
"mile",
|
||||
"miles",
|
||||
"mi",
|
||||
"mi."
|
||||
]
|
||||
},
|
||||
"step": {
|
||||
"factor": 0.18,
|
||||
"aliases": [
|
||||
"step",
|
||||
"steps"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"convertable": false
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import json
|
||||
import csv
|
||||
import os
|
||||
|
||||
|
||||
def activity_log_path(activity: str) -> str:
|
||||
"""
|
||||
Returns the path of the activity log file in .csv format
|
||||
:param activity:
|
||||
:return:
|
||||
"""
|
||||
filename = os.path.join("activity_logs", f"{activity}.csv")
|
||||
return filename
|
||||
|
||||
|
||||
def activity_units_path(activity: str) -> str:
|
||||
"""
|
||||
Returns the path of the activity activities file in .json format
|
||||
:param activity:
|
||||
:return:
|
||||
"""
|
||||
filename = os.path.join("activities", f"{activity}.json")
|
||||
return filename
|
||||
|
||||
|
||||
def is_convertable(activity: str) -> bool:
|
||||
"""
|
||||
Returns True if the activity has multiple units that needed to be converted.
|
||||
i.e. distance may be sent in feet, but would require to be converted in meters.
|
||||
:param activity:
|
||||
:return:
|
||||
"""
|
||||
filename = activity_units_path(activity)
|
||||
raw_data = json.load(open(filename))
|
||||
return raw_data.get("transformable")
|
||||
|
||||
|
||||
|
||||
def create_activity_log_file(activity: str) -> None:
|
||||
"""
|
||||
Creates the activity log file in .csv format
|
||||
:param activity:
|
||||
:return:
|
||||
"""
|
||||
if not os.path.exists(activity_units_path(activity)):
|
||||
raise ValueError(f"{activity} is not a valid activity")
|
||||
if not os.path.exists("activity_logs"):
|
||||
os.makedirs("activity_logs")
|
||||
filename = activity_log_path(activity)
|
||||
with open(filename, 'a') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow(["timestamp", "username", "value"])
|
||||
|
||||
|
||||
|
||||
def convert_units(activity: str, value: int | float, unit: str) -> float:
|
||||
"""
|
||||
Returns the value of activity in a factor of 1, i.e. converting feet to meters
|
||||
:param activity:
|
||||
:param value:
|
||||
:param unit:
|
||||
:return:
|
||||
"""
|
||||
filename = activity_units_path(activity)
|
||||
if not os.path.exists(filename):
|
||||
raise ValueError(f"{activity} is not a valid activity")
|
||||
|
||||
# Consider None unit value as factor of 1
|
||||
if unit is None:
|
||||
return value
|
||||
|
||||
raw_data = json.load(open(filename))
|
||||
units_data = raw_data.get("units")
|
||||
|
||||
for unit_name, data in units_data.items():
|
||||
if unit in data["aliases"]:
|
||||
return value * data["factor"]
|
||||
|
||||
raise ValueError(f"{unit} is not a valid unit")
|
||||
|
||||
|
||||
def log_activity(
|
||||
timestamp: int,
|
||||
username: str,
|
||||
activity: str,
|
||||
value: int | float,
|
||||
unit: str | None
|
||||
) -> None:
|
||||
"""
|
||||
Logs the activity in .csv file
|
||||
:param timestamp:
|
||||
:param username:
|
||||
:param activity:
|
||||
:param value:
|
||||
:param unit:
|
||||
:return:
|
||||
"""
|
||||
if value <= 0 or not isinstance(value, int | float):
|
||||
raise ValueError(f"{value} is not a valid value")
|
||||
filename = activity_log_path(activity)
|
||||
if not os.path.exists(filename):
|
||||
create_activity_log_file(activity)
|
||||
if is_convertable(activity):
|
||||
converted_value = round(convert_units(activity, value, unit), 2)
|
||||
else:
|
||||
converted_value = round(value, 2)
|
||||
with open(filename, 'a') as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([timestamp, username, converted_value])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
log_activity(
|
||||
timestamp=0,
|
||||
username="test",
|
||||
activity="climb",
|
||||
value=1,
|
||||
unit="m"
|
||||
)
|
||||
@@ -0,0 +1,78 @@
|
||||
import os
|
||||
import discord
|
||||
from redbot.core import Config, commands
|
||||
from .activity_logger import log_activity, activity_log_path, activity_units_path
|
||||
|
||||
|
||||
|
||||
class FitnessCog(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
# Some hardcoded values, this probably should be changed with redbot config. IDK how to use it or what's in it.
|
||||
default_threads = "1457402451530350727,1328363409648910356"
|
||||
self.threads_id = list(map(int, os.getenv("THREADS_ID", default_threads).split(",")))
|
||||
self.confirm_reactions = os.getenv("CONFIRMATION_REACTIONS", "🐈💨")
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message):
|
||||
if message.author.bot:
|
||||
return
|
||||
if message.channel.id not in self.threads_id:
|
||||
return
|
||||
if not message.content.startswith("!"):
|
||||
return
|
||||
|
||||
# region Fitness commands
|
||||
if message.content.startswith("!getunits"):
|
||||
await self.get_units_file(message)
|
||||
return
|
||||
if message.content.startswith("!getlog"):
|
||||
await self.get_log_file(message)
|
||||
return
|
||||
# Make sure this one is past every other ! commands
|
||||
await self.fitness_activity_log(message)
|
||||
return
|
||||
# endregion Fitness commands
|
||||
|
||||
# region Fitness commands functions
|
||||
@staticmethod
|
||||
async def get_units_file(message: discord.Message):
|
||||
try:
|
||||
raw = message.content[1:].lower().strip().split(" ", 1)
|
||||
filename = activity_units_path(raw[1])
|
||||
await message.channel.send(file=discord.File(fp=filename))
|
||||
except Exception as e:
|
||||
await message.channel.send(str(e))
|
||||
|
||||
@staticmethod
|
||||
async def get_log_file(message: discord.Message):
|
||||
try:
|
||||
raw = message.content[1:].lower().strip().split(" ", 1)
|
||||
filename = activity_log_path(raw[1])
|
||||
await message.channel.send(file=discord.File(fp=filename))
|
||||
except Exception as e:
|
||||
await message.channel.send(str(e))
|
||||
|
||||
async def log_fitness(self, message):
|
||||
try:
|
||||
raw = message.content[1:].lower().strip().split(" ", 2)
|
||||
|
||||
timestamp = int(message.created_at.timestamp())
|
||||
username = message.author.name
|
||||
activity = raw[0]
|
||||
value = float(raw[1])
|
||||
unit = raw[2] if len(raw) > 2 else "m"
|
||||
|
||||
log_activity(timestamp, username, activity, value, unit)
|
||||
|
||||
for r in self.confirm_reactions:
|
||||
await message.add_reaction(r)
|
||||
|
||||
except Exception as e:
|
||||
await message.reply(str(e))
|
||||
# endregion Fitness commands functions
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(FitnessCog(bot))
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import random
|
||||
import json
|
||||
import asyncio
|
||||
from os import environ
|
||||
import openai
|
||||
from openai import OpenAIError
|
||||
from .weather import time_now, get_current_weather, get_weather_forecast
|
||||
from .tools_description import TOOLS
|
||||
from .debug_stuff import debug
|
||||
from weather import time_now, get_current_weather, get_weather_forecast
|
||||
from tools_description import TOOLS
|
||||
|
||||
CALLABLE_FUNCTIONS = {
|
||||
# Dictionary with functions to call.
|
||||
@@ -21,15 +22,13 @@ class Completion:
|
||||
self.__api_key = api_key
|
||||
self.__messages = []
|
||||
|
||||
@debug
|
||||
async def create_completion(self, messages: list):
|
||||
self.__messages = messages
|
||||
async def create_completion(self):
|
||||
model = self.__model
|
||||
try:
|
||||
client = openai.AsyncClient(api_key=self.__api_key)
|
||||
completion_kwargs = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"messages": self.__messages,
|
||||
"max_tokens": 4096,
|
||||
"temperature": 0.7,
|
||||
"presence_penalty": 0.5,
|
||||
@@ -47,7 +46,7 @@ class Completion:
|
||||
func_args = json.loads(i_call.function.arguments)
|
||||
tool_call_id = i_call.id
|
||||
self.function_manager(func_name, func_args, tool_call_id)
|
||||
return self.create_completion(messages=self.__messages)
|
||||
return await self.create_completion()
|
||||
return response_content
|
||||
except OpenAIError as e:
|
||||
return self.get_error_message(error_message=str(e), error_type="OpenAIError")
|
||||
@@ -81,3 +80,14 @@ class Completion:
|
||||
result = CALLABLE_FUNCTIONS[func_name](**func_kwargs)
|
||||
self.append_message(role="tool", content=result, tool_call_id=tool_call_id)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def main():
|
||||
test_message = input('Your input: ')
|
||||
completion = Completion(model='gpt-4.1-mini', api_key=environ.get('OPENAI_API_KEY'))
|
||||
completion.append_message(role='user', content=test_message)
|
||||
result = await completion.create_completion()
|
||||
print(result)
|
||||
|
||||
asyncio.run(main())
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
TOOLS = [
|
||||
# time_now
|
||||
{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
'name': 'time_now',
|
||||
'description': 'Get current date and time in UTC timezone.',
|
||||
'description': '''
|
||||
Get current date and time in UTC timezone.
|
||||
Use this before get_weather_forecast function if user gave you date of forecast
|
||||
instead of number of days.
|
||||
''',
|
||||
}
|
||||
},
|
||||
# get_current_weather
|
||||
{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
@@ -32,6 +38,7 @@ TOOLS = [
|
||||
'strict': True
|
||||
}
|
||||
},
|
||||
# get_weather_forecast
|
||||
{
|
||||
'type': 'function',
|
||||
'function': {
|
||||
@@ -41,7 +48,8 @@ TOOLS = [
|
||||
forecast and weather alert as json. The data is returned as a Forecast Object.
|
||||
Forecast object contains astronomy data, day weather forecast and hourly interval weather information
|
||||
for a given city.
|
||||
With a free weather API subscription, only up to three days of forecast can be requested.
|
||||
Number of days could be less, depending on the API subscription plan.
|
||||
With a free plan only up to three days of forecast would be returned.
|
||||
''',
|
||||
'parameters': {
|
||||
'type': 'object',
|
||||
@@ -53,16 +61,15 @@ TOOLS = [
|
||||
e.g: "Copenhagen", or "Copenhagen, Louisiana, US", if needed specifying.
|
||||
'''
|
||||
},
|
||||
'dt': {
|
||||
'type': 'string',
|
||||
'days': {
|
||||
'type': 'integer',
|
||||
'description': '''
|
||||
The date up until to request the forecast in YYYY-MM-DD format.
|
||||
Check the **time_now** function first if you unsure which date it is.
|
||||
Number of days of forecast between 0 and 13, where 0 is only today, 1 up to tomorrow, etc.
|
||||
'''
|
||||
},
|
||||
},
|
||||
'required': [
|
||||
'location', 'dt'
|
||||
'location', 'days'
|
||||
],
|
||||
'additionalProperties': False
|
||||
},
|
||||
|
||||
+7
-11
@@ -2,27 +2,26 @@ from datetime import datetime, timezone
|
||||
from os import environ
|
||||
import requests
|
||||
import json
|
||||
from .debug_stuff import debug
|
||||
|
||||
#WEATHER_API_KEY = environ.get('WEATHER_API_KEY')
|
||||
URL = 'http://api.weatherapi.com/v1'
|
||||
|
||||
|
||||
@debug
|
||||
def time_now() -> str:
|
||||
return str(datetime.now(timezone.utc))
|
||||
|
||||
|
||||
@debug
|
||||
def get_current_weather(location: str) -> str:
|
||||
weather = Weather(location=location)
|
||||
return json.dumps(weather.realtime())
|
||||
|
||||
|
||||
@debug
|
||||
def get_weather_forecast(location: str, days: int = 14, dt: str = '2025-03-24') -> str:
|
||||
def get_weather_forecast(location: str, days: int = None) -> str:
|
||||
days += 1
|
||||
days = max(1, days)
|
||||
days = min(14, days)
|
||||
weather = Weather(location=location)
|
||||
return json.dumps(weather.forecast(days=days, dt=dt))
|
||||
return json.dumps(weather.forecast(days=days))
|
||||
|
||||
|
||||
class Weather:
|
||||
@@ -39,7 +38,6 @@ class Weather:
|
||||
response = requests.get(url=f'{URL}{method}', params=params)
|
||||
return response.json()
|
||||
|
||||
@debug
|
||||
def realtime(self):
|
||||
method = '/current.json'
|
||||
params = {
|
||||
@@ -48,19 +46,17 @@ class Weather:
|
||||
}
|
||||
return self.make_request(method=method, params=params)
|
||||
|
||||
@debug
|
||||
def forecast(self, days: int = 14, dt: str = '2025-03-24'):
|
||||
def forecast(self, days: int = 14):
|
||||
method = '/forecast.json'
|
||||
params = {
|
||||
'key': self.api_key,
|
||||
'q': self.location,
|
||||
'days': days,
|
||||
'dt': dt,
|
||||
}
|
||||
return self.make_request(method=method, params=params)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_weather = Weather('Aqtobe')
|
||||
result = json.dumps(test_weather.forecast(days=14, dt='2025-03-24'), indent=2)
|
||||
result = json.dumps(test_weather.forecast(days=13), indent=2)
|
||||
print(result)
|
||||
|
||||
Reference in New Issue
Block a user