Author SHA1 Message Date
T-BENZIN 8bec9a159b Fixed wrong json key being pulled (I forgot to replace the old name) 2026-02-04 12:10:34 +01:00
AllfatherHatt ee77279d6e Merge pull request 'Fixed minor import issue' (#12) from minor-import-fix into master
Reviewed-on: #12
2026-02-04 11:46:39 +01:00
AllfatherHatt 1b67240976 Fixed minor import issue 2026-02-04 11:44:44 +01:00
AllfatherHatt 16a19e6913 Merge pull request 'Added cog for fitness activity logging feature.' (#11) from feature_fitness_logger into master
Reviewed-on: #11
Reviewed-by: AllfatherHatt <[email protected]>
2026-02-04 11:14:40 +01:00
T-BENZIN 4ae03c9b9d Added cog for fitness activity logging feature.
At its starting state it would accept following commands:
- !getunits <activity>: Would send a JSON file with activity units via Discord message. Those files are responsible for setting up a fitness activity to log.
- !getlog <activity>: Would send a CSV file with fitness activity log. Those files contain records with timestamp, username, and reported activity measurements.
- !<activity> <value> <unit (optional)>: Would create a record with your fitness activity in corresponding CSV log file. If you choose to report different unit (feet, for example), it is going to convert as specified in its corresponding JSON file with units (to meters, for example) if the activity specified as convertable. Non-convertable activities' units are ignored.
2026-01-11 10:03:55 +05:00
T-BENZIN 074ff8bbb4 Merge pull request 'Made improvements in AI Agent's tool get_forecast_weather' (#8) from dev into master
Reviewed-on: #8
Reviewed-by: AllfatherHatt <[email protected]>
2025-06-18 17:13:22 +02:00
T-BENZIN 4e22274f9b - Removed unused debug import 2025-06-18 20:10:05 +05:00
T-BENZIN a5ed9f9e1c Applied changes made in GitHub 2025-06-17 10:57:19 +05:00
9 changed files with 294 additions and 26 deletions
+1
View File
@@ -2,6 +2,7 @@
cogs made for our Royal butt.
## Our Cogs:
- [FitnessCog](./fitnessCog)
- [RecruitmentCog](./recruitmentCog/)
- [ReginaldCog](./reginaldCog/)
- [TrafficCog](./trafficCog)
+6
View File
@@ -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)
+48
View File
@@ -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"
]
}
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"convertable": false
}
+119
View File
@@ -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("convertable")
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"
)
+78
View File
@@ -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))
+18 -8
View File
@@ -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())
+14 -7
View File
@@ -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
View File
@@ -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)