From e64a1e253604ca55a31d27dd581d1c5b2c40a620 Mon Sep 17 00:00:00 2001 From: Valerie Date: Fri, 23 May 2025 05:01:02 -0400 Subject: [PATCH] Implement Modrinth project tracking with commands to add, remove, and list projects. Introduce background task for update checking and enhance error handling. Update API usage for project and version retrieval. --- modrinthtracker/modrinthtracker.py | 158 ++++++++++++++++++++++------- 1 file changed, 123 insertions(+), 35 deletions(-) diff --git a/modrinthtracker/modrinthtracker.py b/modrinthtracker/modrinthtracker.py index c8b5a6e..a8e0d51 100644 --- a/modrinthtracker/modrinthtracker.py +++ b/modrinthtracker/modrinthtracker.py @@ -1,70 +1,158 @@ import discord import aiohttp -from datetime import timedelta +from datetime import datetime, timedelta +import asyncio from redbot.core import commands, Config, checks -BASE_URL = "https://api.modrinth.com/v2/project/" +BASE_URL = "https://api.modrinth.com/v2" class ModrinthTracker(commands.Cog): def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, identifier=1234567890, force_registration=True) self.config.register_guild(tracked_projects={}) + self.session = None + self.bg_task = None + + async def cog_load(self): + self.session = aiohttp.ClientSession() + self.bg_task = self.bot.loop.create_task(self.update_checker()) + + async def cog_unload(self): + if self.session: + await self.session.close() + if self.bg_task: + self.bg_task.cancel() @commands.group() @checks.admin() async def modrinth(self, ctx): - pass + """Commands for tracking Modrinth projects""" + if ctx.invoked_subcommand is None: + await ctx.send_help() @modrinth.command() async def add(self, ctx, project_id: str, channel: discord.TextChannel): - tracked_projects = await self.config.guild(ctx.guild).tracked_projects() - if project_id in tracked_projects: - await ctx.send("This project is already being tracked.") - return + """Add a Modrinth project to track + + Arguments: + project_id: The Modrinth project ID or slug + channel: The channel to send updates to + """ + try: + # Verify the project exists and get its info + async with self.session.get(f"{BASE_URL}/project/{project_id}") as response: + if response.status != 200: + await ctx.send(f"Error: Project `{project_id}` not found on Modrinth.") + return + project_data = await response.json() - tracked_projects[project_id] = {"channel": channel.id, "latest_version": None} - await self.config.guild(ctx.guild).tracked_projects.set(tracked_projects) - await ctx.send(f"Tracking project `{project_id}` in {channel.mention}.") + # Get the latest version + async with self.session.get(f"{BASE_URL}/project/{project_id}/version") as response: + if response.status != 200: + await ctx.send("Error: Could not fetch version information.") + return + versions = await response.json() + latest_version = versions[0] if versions else None + + tracked_projects = await self.config.guild(ctx.guild).tracked_projects() + if project_id in tracked_projects: + await ctx.send("This project is already being tracked.") + return + + tracked_projects[project_id] = { + "channel": channel.id, + "latest_version": latest_version["id"] if latest_version else None, + "name": project_data["title"] + } + await self.config.guild(ctx.guild).tracked_projects.set(tracked_projects) + await ctx.send(f"Now tracking {project_data['title']} (`{project_id}`) in {channel.mention}.") + + except Exception as e: + await ctx.send(f"An error occurred while adding the project: {str(e)}") @modrinth.command() async def remove(self, ctx, project_id: str): + """Remove a tracked Modrinth project + + Arguments: + project_id: The Modrinth project ID or slug to stop tracking + """ tracked_projects = await self.config.guild(ctx.guild).tracked_projects() if project_id not in tracked_projects: await ctx.send("This project is not being tracked.") return + project_name = tracked_projects[project_id].get("name", project_id) del tracked_projects[project_id] await self.config.guild(ctx.guild).tracked_projects.set(tracked_projects) - await ctx.send(f"Stopped tracking project `{project_id}`.") + await ctx.send(f"Stopped tracking {project_name} (`{project_id}`).") - async def check_updates(self): - async with aiohttp.ClientSession() as session: - tracked_projects = await self.config.guild(ctx.guild).tracked_projects() - for project_id, data in tracked_projects.items(): - url = BASE_URL + project_id - async with session.get(url) as response: - if response.status != 200: - continue + @modrinth.command() + async def list(self, ctx): + """List all tracked Modrinth projects""" + tracked_projects = await self.config.guild(ctx.guild).tracked_projects() + if not tracked_projects: + await ctx.send("No projects are currently being tracked.") + return - project_data = await response.json() - latest_version = project_data.get("latest_version") - if not latest_version or latest_version == data.get("latest_version"): - continue + embed = discord.Embed(title="Tracked Modrinth Projects", color=discord.Color.blue()) + for project_id, data in tracked_projects.items(): + channel = self.bot.get_channel(data["channel"]) + channel_mention = channel.mention if channel else "Unknown channel" + embed.add_field( + name=data.get("name", project_id), + value=f"ID: `{project_id}`\nChannel: {channel_mention}", + inline=False + ) + await ctx.send(embed=embed) - channel = self.bot.get_channel(data["channel"]) - if channel: - await channel.send(f"New update for `{project_data['title']}`: `{latest_version}`\n{project_data['id']}") - - tracked_projects[project_id]["latest_version"] = latest_version - - await self.config.guild(ctx.guild).tracked_projects.set(tracked_projects) - - @commands.Cog.listener() - async def on_ready(self): + async def update_checker(self): + await self.bot.wait_until_ready() while True: - await self.check_updates() - await discord.utils.sleep_until(discord.utils.utcnow().replace(second=0, microsecond=0) + timedelta(minutes=5)) + try: + all_guilds = await self.config.all_guilds() + for guild_id, guild_data in all_guilds.items(): + guild = self.bot.get_guild(guild_id) + if not guild: + continue + + tracked_projects = guild_data.get("tracked_projects", {}) + for project_id, data in tracked_projects.items(): + try: + async with self.session.get(f"{BASE_URL}/project/{project_id}/version") as response: + if response.status != 200: + continue + + versions = await response.json() + if not versions: + continue + + latest_version = versions[0] + if latest_version["id"] == data.get("latest_version"): + continue + + channel = self.bot.get_channel(data["channel"]) + if channel: + embed = discord.Embed( + title=f"New Update for {data.get('name', project_id)}!", + description=f"Version: {latest_version.get('version_number', 'Unknown')}\n\n{latest_version.get('changelog', 'No changelog provided')}", + url=f"https://modrinth.com/project/{project_id}", + color=discord.Color.green(), + timestamp=datetime.now() + ) + await channel.send(embed=embed) + + tracked_projects[project_id]["latest_version"] = latest_version["id"] + await self.config.guild(guild).tracked_projects.set(tracked_projects) + + except Exception as e: + continue + + except Exception as e: + pass + + await asyncio.sleep(300) # Check every 5 minutes async def setup(bot): await bot.add_cog(ModrinthTracker(bot))