From b18355e7490529a59f63c00c7a03308f4d15ba4b Mon Sep 17 00:00:00 2001 From: neoarz Date: Fri, 17 Oct 2025 01:44:26 -0400 Subject: [PATCH] feat(botinfo): contributors!!! --- cogs/botinfo.py | 35 +++++++++++--- utils/__init__.py | 4 +- utils/contributors.py | 105 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 utils/contributors.py diff --git a/cogs/botinfo.py b/cogs/botinfo.py index d1d2e51..994abc2 100644 --- a/cogs/botinfo.py +++ b/cogs/botinfo.py @@ -6,6 +6,7 @@ from discord.ext.commands import Context from datetime import datetime import time import pytz +from utils.contributors import generate_contributors_image class FeedbackForm(discord.ui.Modal, title="Feedback"): @@ -125,6 +126,9 @@ class BotInfo(commands.Cog, name="botinfo"): @app_commands.allowed_contexts(guilds=True, dms=True, private_channels=True) @app_commands.allowed_installs(guilds=True, users=True) async def botinfo(self, context: Context) -> None: + if context.interaction: + await context.interaction.response.defer(ephemeral=False) + ny_tz = pytz.timezone('America/New_York') current_time = datetime.now(ny_tz).strftime("%m/%d/%y, %I:%M %p") @@ -137,21 +141,38 @@ class BotInfo(commands.Cog, name="botinfo"): f"**Prefix:** / (Slash Commands) or {self.bot.bot_prefix} for normal commands" ) - embed = discord.Embed( + embed1 = discord.Embed( title="Syntrel Discord Bot", description=description_text, color=0x7289DA, ) - embed.set_author(name="Syntrel", icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true") - embed.set_image(url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png") - embed.set_footer(text=f"neoarz • {current_time}", icon_url="https://yes.nighty.works/raw/P1Us35.webp") + embed1.set_author(name="Syntrel", icon_url="https://github.com/neoarz/Syntrel/blob/main/assets/icon.png?raw=true") + embed1.set_image(url="https://github.com/neoarz/Syntrel/raw/main/assets/bannerdark.png") + embed1.set_footer(text=f"neoarz • {current_time}", icon_url="https://yes.nighty.works/raw/P1Us35.webp") + + embed2 = discord.Embed( + title="Contributors", + description="Giving credit where it's due! ", + color=0x7289DA, + ) + + contributors_image = generate_contributors_image() view = BotInfoView(self.bot) - if context.interaction: - await context.interaction.response.send_message(embed=embed, view=view, ephemeral=False) + if contributors_image: + file = discord.File(contributors_image, filename="contributors.png") + embed2.set_image(url="attachment://contributors.png") + + if context.interaction: + await context.interaction.followup.send(embeds=[embed1, embed2], file=file, view=view) + else: + await context.send(embeds=[embed1, embed2], file=file, view=view) else: - await context.send(embed=embed, view=view) + if context.interaction: + await context.interaction.followup.send(embeds=[embed1, embed2], view=view) + else: + await context.send(embeds=[embed1, embed2], view=view) async def setup(bot) -> None: diff --git a/utils/__init__.py b/utils/__init__.py index 74c5cac..9ac8be5 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -2,8 +2,10 @@ from .ascii_art import ascii, ascii_plain, gradient_text, gradient_text_selectiv from .logging import LoggingFormatter, setup_logger from .time import get_uptime from .signal import setup_signal_handlers +from .contributors import generate_contributors_image __all__ = [ 'ascii', 'ascii_plain', 'gradient_text', 'gradient_text_selective', - 'LoggingFormatter', 'setup_logger', 'get_uptime', 'setup_signal_handlers' + 'LoggingFormatter', 'setup_logger', 'get_uptime', 'setup_signal_handlers', + 'generate_contributors_image' ] \ No newline at end of file diff --git a/utils/contributors.py b/utils/contributors.py new file mode 100644 index 0000000..b47d34d --- /dev/null +++ b/utils/contributors.py @@ -0,0 +1,105 @@ +from io import BytesIO +from math import ceil + +import requests +from PIL import Image + + +def fetch_contributors(owner, repo): + contributors = [] + page = 1 + + while True: + url = f"https://api.github.com/repos/{owner}/{repo}/contributors" + params = {"page": page, "per_page": 100} + + headers = { + "User-Agent": "github-contributors-graph", + "Accept": "application/vnd.github.v3+json" + } + + response = requests.get(url, headers=headers, params=params) + + if not response.ok: + return [] + + data = response.json() + + if not data: + break + + contributors.extend(data) + page += 1 + + if response.headers.get('X-RateLimit-Remaining') == '0': + break + + return contributors + + +def download_avatar(avatar_url, size): + try: + if "avatars.githubusercontent.com" in avatar_url: + avatar_url = f"{avatar_url}?s={size}" + + response = requests.get(avatar_url, timeout=10) + + if not response.ok: + return None + + img = Image.open(BytesIO(response.content)) + + if img.size != (size, size): + img = img.resize((size, size), Image.Resampling.LANCZOS) + + return img + + except Exception: + return None + + +def generate_contributors_image(owner="neoarz", repo="syntrel", size=64, images_per_row=20): + contributors = fetch_contributors(owner, repo) + + if not contributors: + return None + + images = [] + + for contributor in contributors: + avatar_url = contributor.get("avatar_url") + + if not avatar_url: + continue + + img = download_avatar(avatar_url, size) + + if img is None: + continue + + images.append(img) + + if not images: + return None + + actual_images_per_row = min(len(images), images_per_row) + width = size * actual_images_per_row + row_count = ceil(len(images) / images_per_row) + height = size * row_count + + canvas = Image.new("RGB", (width, height), color="black") + + for idx, img in enumerate(images): + col = idx % images_per_row + row = idx // images_per_row + x = col * size + y = row * size + + canvas.paste(img, (x, y)) + + buffer = BytesIO() + canvas.save(buffer, "PNG") + buffer.seek(0) + + return buffer +