import os from pathlib import Path import discord from redbot.core import commands from redbot.core.data_manager import cog_data_path from .activity_logger import log_activity class FitnessCog(commands.Cog): def __init__(self, bot): self.bot = bot default_threads = "1457402451530350727,1328363409648910356" threads_raw = os.getenv("THREADS_ID", default_threads) self.threads_id: list[int] = [] for x in threads_raw.split(","): x = x.strip() if x.isdigit(): self.threads_id.append(int(x)) self.confirm_reactions = os.getenv("CONFIRMATION_REACTIONS", "🐈💨") # Units are shipped with the cog in: fitnessCog/activities/*.json self.units_dir: Path = Path(__file__).parent / "activities" # Logs are writable and belong in Red's data dir self.logs_dir: Path = cog_data_path(self) / "activity_logs" self.logs_dir.mkdir(parents=True, exist_ok=True) @commands.Cog.listener() async def on_message(self, message: discord.Message): if message.author.bot: return if message.channel.id not in self.threads_id: return content = (message.content or "").strip() if not content.startswith("!"): return cmdline = content[1:].strip().lower() if not cmdline: return cmd, *rest = cmdline.split(" ", 1) arg = rest[0].strip() if rest else "" if cmd == "getunits": await self.get_units_file(message, arg) return if cmd == "getlog": await self.get_log_file(message, arg) return await self.log_fitness(message) async def get_units_file(self, message: discord.Message, activity: str): try: if not activity: await message.channel.send( "Usage: `!getunits ` (example: `!getunits pushups`)" ) return activity = activity.lower().strip() path = self.units_dir / f"{activity}.json" if not path.exists(): await message.channel.send(f"No units JSON found for `{activity}`.") return await message.channel.send(file=discord.File(fp=str(path))) except Exception as e: await message.channel.send(str(e)) async def get_log_file(self, message: discord.Message, activity: str): try: if not activity: await message.channel.send( "Usage: `!getlog ` (example: `!getlog pushups`)" ) return activity = activity.lower().strip() path = self.logs_dir / f"{activity}.csv" if not path.exists(): await message.channel.send(f"No log CSV found for `{activity}`.") return await message.channel.send(file=discord.File(fp=str(path))) except Exception as e: await message.channel.send(str(e)) async def log_fitness(self, message: discord.Message): try: parts = message.content[1:].strip().split() if len(parts) < 2: await message.reply( "Usage: `! [unit]` (example: `!pushups 20` or `!run 5 km`)" ) return activity = parts[0] try: value = float(parts[1]) except ValueError: await message.reply( "Invalid number. Usage: `! [unit]` (example: `!run 5 km`)" ) return unit = parts[2] if len(parts) > 2 else "m" timestamp = int(message.created_at.timestamp()) username = message.author.name log_activity( timestamp, username, activity, value, unit, units_dir=self.units_dir, logs_dir=self.logs_dir, ) for r in self.confirm_reactions: await message.add_reaction(r) except Exception as e: await message.reply(str(e)) def setup(bot): bot.add_cog(FitnessCog(bot))