186 lines
No EOL
7.3 KiB
Python
186 lines
No EOL
7.3 KiB
Python
import asyncio
|
|
import json
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, Optional
|
|
|
|
import aiohttp
|
|
import discord
|
|
from redbot.core import Config, commands
|
|
from redbot.core.bot import Red
|
|
from redbot.core.utils.chat_formatting import box, humanize_list
|
|
|
|
|
|
class RepoUpdates(commands.Cog):
|
|
"""Monitor cog repositories for updates and post notifications."""
|
|
|
|
def __init__(self, bot: Red):
|
|
self.bot = bot
|
|
self.config = Config.get_conf(self, identifier=8927348923)
|
|
self.session = aiohttp.ClientSession()
|
|
self.bg_task = None
|
|
|
|
default_global = {
|
|
"check_interval": 300, # 5 minutes in seconds
|
|
"channel_id": None,
|
|
"repos": {} # Dict[str, Dict[str, str]] - repo_url: {last_commit: str}
|
|
}
|
|
|
|
self.config.register_global(**default_global)
|
|
|
|
def cog_unload(self):
|
|
if self.bg_task:
|
|
self.bg_task.cancel()
|
|
asyncio.create_task(self.session.close())
|
|
|
|
async def initialize(self):
|
|
"""Start the background task."""
|
|
self.bg_task = self.bot.loop.create_task(self.check_updates_loop())
|
|
|
|
@commands.group()
|
|
@commands.admin_or_permissions(administrator=True)
|
|
async def repoupdate(self, ctx: commands.Context):
|
|
"""Commands for repository update notifications."""
|
|
pass
|
|
|
|
@repoupdate.command()
|
|
async def channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
|
"""Set the channel for repository update notifications."""
|
|
await self.config.channel_id.set(channel.id)
|
|
await ctx.send(f"Update notifications will be sent to {channel.mention}")
|
|
|
|
@repoupdate.command()
|
|
async def interval(self, ctx: commands.Context, seconds: int):
|
|
"""Set how often to check for updates (in seconds)."""
|
|
if seconds < 60:
|
|
await ctx.send("Interval must be at least 60 seconds.")
|
|
return
|
|
await self.config.check_interval.set(seconds)
|
|
await ctx.send(f"Update check interval set to {seconds} seconds.")
|
|
|
|
@repoupdate.command()
|
|
async def addrepo(self, ctx: commands.Context, name: str, repo_url: str):
|
|
"""Add a repository to monitor."""
|
|
async with self.config.repos() as repos:
|
|
if name in repos:
|
|
await ctx.send("A repository with that name already exists.")
|
|
return
|
|
|
|
# Validate the repo URL and get the latest commit
|
|
try:
|
|
api_url = repo_url.replace("github.com", "api.github.com/repos")
|
|
if api_url.endswith("/"):
|
|
api_url = api_url[:-1]
|
|
api_url += "/commits"
|
|
|
|
async with self.session.get(api_url) as resp:
|
|
if resp.status != 200:
|
|
await ctx.send("Failed to fetch repository information. Please check the URL.")
|
|
return
|
|
commits = await resp.json()
|
|
if not commits:
|
|
await ctx.send("No commits found in the repository.")
|
|
return
|
|
latest_commit = commits[0]["sha"]
|
|
|
|
repos[name] = {
|
|
"url": repo_url,
|
|
"last_commit": latest_commit
|
|
}
|
|
await ctx.send(f"Added repository: {name}")
|
|
except Exception as e:
|
|
await ctx.send(f"Error adding repository: {str(e)}")
|
|
|
|
@repoupdate.command()
|
|
async def removerepo(self, ctx: commands.Context, name: str):
|
|
"""Remove a repository from monitoring."""
|
|
async with self.config.repos() as repos:
|
|
if name not in repos:
|
|
await ctx.send("Repository not found.")
|
|
return
|
|
del repos[name]
|
|
await ctx.send(f"Removed repository: {name}")
|
|
|
|
@repoupdate.command()
|
|
async def listrepos(self, ctx: commands.Context):
|
|
"""List all monitored repositories."""
|
|
repos = await self.config.repos()
|
|
if not repos:
|
|
await ctx.send("No repositories are being monitored.")
|
|
return
|
|
|
|
msg = "Monitored Repositories:\n"
|
|
for name, data in repos.items():
|
|
msg += f"\n• {name}: {data['url']}"
|
|
|
|
await ctx.send(box(msg))
|
|
|
|
async def check_updates_loop(self):
|
|
"""Background loop to check for repository updates."""
|
|
await self.bot.wait_until_ready()
|
|
while True:
|
|
try:
|
|
await self.check_updates()
|
|
except Exception as e:
|
|
print(f"Error checking updates: {str(e)}")
|
|
|
|
interval = await self.config.check_interval()
|
|
await asyncio.sleep(interval)
|
|
|
|
async def check_updates(self):
|
|
"""Check all repositories for updates."""
|
|
channel_id = await self.config.channel_id()
|
|
if not channel_id:
|
|
return
|
|
|
|
channel = self.bot.get_channel(channel_id)
|
|
if not channel:
|
|
return
|
|
|
|
async with self.config.repos() as repos:
|
|
for name, data in repos.items():
|
|
try:
|
|
api_url = data["url"].replace("github.com", "api.github.com/repos")
|
|
if api_url.endswith("/"):
|
|
api_url = api_url[:-1]
|
|
api_url += "/commits"
|
|
|
|
async with self.session.get(api_url) as resp:
|
|
if resp.status != 200:
|
|
continue
|
|
commits = await resp.json()
|
|
if not commits:
|
|
continue
|
|
|
|
latest_commit = commits[0]["sha"]
|
|
if latest_commit != data["last_commit"]:
|
|
# Get the changes
|
|
changes = []
|
|
for commit in commits:
|
|
if commit["sha"] == data["last_commit"]:
|
|
break
|
|
changes.append(f"• {commit['commit']['message']}")
|
|
|
|
# Create and send the update embed
|
|
embed = discord.Embed(
|
|
title=f"Repository Update: {name}",
|
|
url=data["url"],
|
|
color=discord.Color.green(),
|
|
timestamp=datetime.now()
|
|
)
|
|
|
|
if changes:
|
|
embed.add_field(
|
|
name="Changes",
|
|
value="\n".join(changes[:10]), # Show up to 10 changes
|
|
inline=False
|
|
)
|
|
if len(changes) > 10:
|
|
embed.set_footer(text=f"And {len(changes) - 10} more changes...")
|
|
|
|
await channel.send(embed=embed)
|
|
data["last_commit"] = latest_commit
|
|
|
|
except Exception as e:
|
|
print(f"Error checking repository {name}: {str(e)}")
|
|
continue |