import discord import aiohttp from datetime import datetime, timedelta import asyncio from redbot.core import commands, Config, checks 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): """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): """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() # 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}.") # Post the current version information if latest_version: embed = discord.Embed( title=f"Current Version of {project_data['title']}", 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.blue(), timestamp=datetime.now() ) # Add project icon as thumbnail if available if project_data.get("icon_url"): embed.set_thumbnail(url=project_data["icon_url"]) # Add featured gallery image if available if project_data.get("gallery"): for image in project_data["gallery"]: if image.get("featured", False): embed.set_image(url=image["url"]) break # Add project details categories = ", ".join(f"`{cat}`" for cat in project_data.get("categories", [])) if categories: embed.add_field(name="Categories", value=categories, inline=True) downloads = project_data.get("downloads", 0) followers = project_data.get("followers", 0) stats = f"šŸ“„ {downloads:,} Downloads\nšŸ‘„ {followers:,} Followers" embed.add_field(name="Statistics", value=stats, inline=True) # Add version details loaders = ", ".join(f"`{loader}`" for loader in latest_version.get("loaders", [])) if loaders: embed.add_field(name="Supported Loaders", value=loaders, inline=True) game_versions = ", ".join(f"`{ver}`" for ver in latest_version.get("game_versions", [])) if game_versions: embed.add_field(name="Game Versions", value=game_versions, inline=True) embed.set_footer(text="Tracking Started") await channel.send(embed=embed) else: await channel.send("No version information is currently available for this project.") 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_name} (`{project_id}`).") @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 embed = discord.Embed( title="šŸ“‹ Tracked Modrinth Projects", color=discord.Color.blue(), timestamp=datetime.now() ) for project_id, data in tracked_projects.items(): channel = self.bot.get_channel(data["channel"]) channel_mention = channel.mention if channel else "Unknown channel" # Get current project info try: async with self.session.get(f"{BASE_URL}/project/{project_id}") as response: if response.status == 200: project_data = await response.json() downloads = project_data.get("downloads", 0) followers = project_data.get("followers", 0) description = f"**ID:** `{project_id}`\n**Channel:** {channel_mention}\nšŸ“„ {downloads:,} Downloads\nšŸ‘„ {followers:,} Followers" embed.add_field( name=data.get("name", project_id), value=description, inline=False ) else: embed.add_field( name=data.get("name", project_id), value=f"**ID:** `{project_id}`\n**Channel:** {channel_mention}", inline=False ) except Exception: embed.add_field( name=data.get("name", project_id), value=f"**ID:** `{project_id}`\n**Channel:** {channel_mention}", inline=False ) embed.set_footer(text=f"Total Projects: {len(tracked_projects)}") await ctx.send(embed=embed) async def update_checker(self): await self.bot.wait_until_ready() while True: 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: # Get project info for the embed async with self.session.get(f"{BASE_URL}/project/{project_id}") as response: if response.status != 200: continue project_data = await response.json() 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() ) # Add project icon as thumbnail if project_data.get("icon_url"): embed.set_thumbnail(url=project_data["icon_url"]) # Add version details loaders = ", ".join(f"`{loader}`" for loader in latest_version.get("loaders", [])) if loaders: embed.add_field(name="Supported Loaders", value=loaders, inline=True) game_versions = ", ".join(f"`{ver}`" for ver in latest_version.get("game_versions", [])) if game_versions: embed.add_field(name="Game Versions", value=game_versions, inline=True) # Add download info downloads = project_data.get("downloads", 0) embed.add_field(name="Total Downloads", value=f"šŸ“„ {downloads:,}", inline=True) embed.set_footer(text="Update Released") 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))