diff --git a/api_service/webhook_endpoints.py b/api_service/webhook_endpoints.py index 9ebf85c..693c741 100644 --- a/api_service/webhook_endpoints.py +++ b/api_service/webhook_endpoints.py @@ -2,7 +2,7 @@ import hashlib import hmac import json import logging -from typing import Dict, Any +from typing import Dict, Any, Optional from fastapi import APIRouter, Request, HTTPException, Depends, Header, Path import discord # For Color diff --git a/cogs/economy/earning.py b/cogs/economy/earning.py index 0c7b4cd..9739d03 100644 --- a/cogs/economy/earning.py +++ b/cogs/economy/earning.py @@ -16,7 +16,7 @@ class EarningCommands(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - @commands.hybrid_command(name="daily", description="Claim your daily reward.") + @commands.command(name="daily", description="Claim your daily reward.") async def daily(self, ctx: commands.Context): """Allows users to claim a daily currency reward.""" user_id = ctx.author.id @@ -54,7 +54,7 @@ class EarningCommands(commands.Cog): await ctx.send(embed=embed) - @commands.hybrid_command(name="beg", description="Beg for some spare change.") + @commands.command(name="beg", description="Beg for some spare change.") async def beg(self, ctx: commands.Context): """Allows users to beg for a small amount of currency with a chance of success.""" user_id = ctx.author.id @@ -102,7 +102,7 @@ class EarningCommands(commands.Cog): ) await ctx.send(embed=embed) - @commands.hybrid_command(name="work", description="Do some work for a guaranteed reward.") + @commands.command(name="work", description="Do some work for a guaranteed reward.") async def work(self, ctx: commands.Context): """Allows users to perform work for a small, guaranteed reward.""" user_id = ctx.author.id @@ -161,7 +161,7 @@ class EarningCommands(commands.Cog): embed.add_field(name="New Balance", value=f"${current_balance:,}", inline=False) await ctx.send(embed=embed) - @commands.hybrid_command(name="scavenge", description="Scavenge around for some spare change.") # Renamed to avoid conflict + @commands.command(name="scavenge", description="Scavenge around for some spare change.") # Renamed to avoid conflict async def scavenge(self, ctx: commands.Context): # Renamed function """Allows users to scavenge for a small chance of finding money.""" user_id = ctx.author.id diff --git a/cogs/games_cog.py b/cogs/games_cog.py index a21d746..741f2fd 100644 --- a/cogs/games_cog.py +++ b/cogs/games_cog.py @@ -975,7 +975,7 @@ class GamesCog(commands.Cog, name="Games"): # --- Prefix Commands (Legacy Support) --- - @commands.command(name="coinflipbet") + @commands.command(name="coinflipbet", add_to_app_commands=False) async def coinflipbet_prefix(self, ctx: commands.Context, opponent: discord.Member): """(Prefix) Challenge another user to a coin flip game.""" initiator = ctx.author @@ -989,25 +989,25 @@ class GamesCog(commands.Cog, name="Games"): message = await ctx.send(initial_message, view=view) view.message = message - @commands.command(name="coinflip") + @commands.command(name="coinflip", add_to_app_commands=False) async def coinflip_prefix(self, ctx: commands.Context): """(Prefix) Flip a coin.""" result = flip_coin() await ctx.send(f"The coin landed on **{result}**! 🪙") - @commands.command(name="roll") + @commands.command(name="roll", add_to_app_commands=False) async def roll_prefix(self, ctx: commands.Context): """(Prefix) Roll a dice.""" result = roll_dice() await ctx.send(f"You rolled a **{result}**! 🎲") - @commands.command(name="magic8ball") + @commands.command(name="magic8ball", add_to_app_commands=False) async def magic8ball_prefix(self, ctx: commands.Context, *, question: str): """(Prefix) Ask the magic 8 ball.""" response = magic8ball_response() await ctx.send(f"🎱 {response}") - @commands.command(name="tictactoe") + @commands.command(name="tictactoe", add_to_app_commands=False) async def tictactoe_prefix(self, ctx: commands.Context, opponent: discord.Member): """(Prefix) Challenge another user to Tic-Tac-Toe.""" initiator = ctx.author @@ -1021,7 +1021,7 @@ class GamesCog(commands.Cog, name="Games"): message = await ctx.send(initial_message, view=view) view.message = message - @commands.command(name="tictactoebot") + @commands.command(name="tictactoebot", add_to_app_commands=False) async def tictactoebot_prefix(self, ctx: commands.Context, difficulty: str = "minimax"): """(Prefix) Play Tic-Tac-Toe against the bot.""" difficulty_value = difficulty.lower() @@ -1057,7 +1057,7 @@ class GamesCog(commands.Cog, name="Games"): ) view.message = message - @commands.command(name="rpschallenge") + @commands.command(name="rpschallenge", add_to_app_commands=False) async def rpschallenge_prefix(self, ctx: commands.Context, opponent: discord.Member): """(Prefix) Challenge another user to Rock-Paper-Scissors.""" initiator = ctx.author @@ -1071,7 +1071,7 @@ class GamesCog(commands.Cog, name="Games"): message = await ctx.send(initial_message, view=view) view.message = message - @commands.command(name="rps") + @commands.command(name="rps", add_to_app_commands=False) async def rps_prefix(self, ctx: commands.Context, choice: str): """(Prefix) Play Rock-Paper-Scissors against the bot.""" choices = ["Rock", "Paper", "Scissors"] @@ -1099,7 +1099,7 @@ class GamesCog(commands.Cog, name="Games"): f"{result}" ) - @commands.command(name="chess") + @commands.command(name="chess", add_to_app_commands=False) async def chess_prefix(self, ctx: commands.Context, opponent: discord.Member): """(Prefix) Start a game of chess with another user.""" initiator = ctx.author @@ -1120,12 +1120,12 @@ class GamesCog(commands.Cog, name="Games"): asyncio.create_task(view._send_or_update_dm(view.white_player)) asyncio.create_task(view._send_or_update_dm(view.black_player)) - @commands.command(name="hangman") + @commands.command(name="hangman", add_to_app_commands=False) async def hangman_prefix(self, ctx: commands.Context): """(Prefix) Play a game of Hangman.""" await play_hangman(self.bot, ctx.channel, ctx.author) - @commands.command(name="guess") + @commands.command(name="guess", add_to_app_commands=False) async def guess_prefix(self, ctx: commands.Context, guess: int): """(Prefix) Guess a number between 1 and 100.""" number_to_guess = random.randint(1, 100) diff --git a/cogs/git_monitor_cog.py b/cogs/git_monitor_cog.py index 4d864d7..793865e 100644 --- a/cogs/git_monitor_cog.py +++ b/cogs/git_monitor_cog.py @@ -22,8 +22,8 @@ log = logging.getLogger(__name__) # Helper to parse repo URL and determine platform def parse_repo_url(url: str) -> tuple[Optional[str], Optional[str]]: """Parses a Git repository URL to extract platform and a simplified repo identifier.""" - # Changed from +? to + for the repo name part for robustness, though unlikely to be the issue for simple URLs. - github_match = re.match(r"https^(?:https?://)?(?:www\.)?github\.com/([\w.-]+/[\w.-]+)(?:\.git)?/?$", url) + # Fixed regex pattern for GitHub URLs + github_match = re.match(r"^(?:https?://)?(?:www\.)?github\.com/([\w.-]+/[\w.-]+)(?:\.git)?/?$", url) if github_match: return "github", github_match.group(1) @@ -81,7 +81,7 @@ class GitMonitorCog(commands.Cog): if target_branch: params["sha"] = target_branch # GitHub uses 'sha' for branch/tag/commit SHA # No 'since_sha' for GitHub commits list. Manual filtering after fetch. - + async with session.get(api_url, params=params) as response: if response.status == 200: commits_payload = await response.json() @@ -91,7 +91,7 @@ class GitMonitorCog(commands.Cog): temp_new_commits = [] # Clear previous if we found the last one continue temp_new_commits.append(commit_item) - + if temp_new_commits: new_commits_data = temp_new_commits latest_fetched_sha = new_commits_data[-1]['sha'] @@ -124,7 +124,7 @@ class GitMonitorCog(commands.Cog): temp_new_commits = [] continue temp_new_commits.append(commit_item) - + if temp_new_commits: new_commits_data = temp_new_commits latest_fetched_sha = new_commits_data[-1]['id'] @@ -161,7 +161,7 @@ class GitMonitorCog(commands.Cog): verified_status = "Verified" if verification.get('verified') else "Unverified" if verification.get('reason') and verification.get('reason') != 'unsigned': verified_status += f" ({verification.get('reason')})" - + # Files changed and stats require another API call per commit: GET /repos/{owner}/{repo}/commits/{sha} # This is too API intensive for a simple polling loop. # We will omit detailed file stats for polled GitHub commits for now. @@ -200,7 +200,7 @@ class GitMonitorCog(commands.Cog): embed.add_field(name="Commit", value=f"[`{commit_id_short}`]({commit_url})", inline=True) # embed.add_field(name="Branch", value="default (polling)", inline=True) # Placeholder embed.add_field(name="Changes", value=files_changed_str, inline=False) - + if embed: try: await channel.send(embed=embed) @@ -211,11 +211,11 @@ class GitMonitorCog(commands.Cog): log.error(f"Discord HTTP error sending message for {repo_url}: {dhe}") else: log.warning(f"Channel {channel_id} not found for guild {guild_id} for repo {repo_url}") - + # Update polling status in DB if latest_fetched_sha != last_sha or not new_commits_data : # Update if new sha or just to update timestamp await settings_manager.update_repository_polling_status(repo_id, latest_fetched_sha, datetime.datetime.now(datetime.timezone.utc)) - + # Small delay between processing each repo to be nice to APIs await asyncio.sleep(2) # 2 seconds delay @@ -365,14 +365,14 @@ class GitMonitorCog(commands.Cog): return embed = discord.Embed(title=f"Monitored Repositories for {interaction.guild.name}", color=discord.Color.blue()) - + description_lines = [] for repo in monitored_repos: channel = self.bot.get_channel(repo['notification_channel_id']) channel_mention = channel.mention if channel else f"ID: {repo['notification_channel_id']}" method = repo['monitoring_method'].capitalize() platform = repo['platform'].capitalize() - + # Attempt to get a cleaner repo name if possible _, repo_name_simple = parse_repo_url(repo['repository_url']) display_name = repo_name_simple if repo_name_simple else repo['repository_url'] @@ -384,7 +384,7 @@ class GitMonitorCog(commands.Cog): f"- Channel: {channel_mention}\n" f"- DB ID: `{repo['id']}`" ) - + embed.description = "\n\n".join(description_lines) if len(embed.description) > 4000 : # Discord embed description limit embed.description = embed.description[:3990] + "\n... (list truncated)" diff --git a/settings_manager.py b/settings_manager.py index acd9e27..615c668 100644 --- a/settings_manager.py +++ b/settings_manager.py @@ -1,3 +1,4 @@ +import datetime import asyncpg import redis.asyncio as redis import os diff --git a/test_url_parser.py b/test_url_parser.py new file mode 100644 index 0000000..bd0942f --- /dev/null +++ b/test_url_parser.py @@ -0,0 +1,35 @@ +import re +from typing import Optional, Tuple + +# Copy of the fixed parse_repo_url function +def parse_repo_url(url: str) -> Tuple[Optional[str], Optional[str]]: + """Parses a Git repository URL to extract platform and a simplified repo identifier.""" + # Fixed regex pattern for GitHub URLs + github_match = re.match(r"^(?:https?://)?(?:www\.)?github\.com/([\w.-]+/[\w.-]+)(?:\.git)?/?$", url) + if github_match: + return "github", github_match.group(1) + + gitlab_match = re.match(r"^(?:https?://)?(?:www\.)?gitlab\.com/([\w.-]+(?:/[\w.-]+)+)(?:\.git)?/?$", url) + if gitlab_match: + return "gitlab", gitlab_match.group(1) + return None, None + +# Test URLs +test_urls = [ + "https://github.com/Slipstreamm/discordbot", + "http://github.com/Slipstreamm/discordbot", + "github.com/Slipstreamm/discordbot", + "www.github.com/Slipstreamm/discordbot", + "https://github.com/Slipstreamm/discordbot.git", + "https://gitlab.com/group/project", + "https://gitlab.com/group/subgroup/project", + "invalid-url" +] + +# Test each URL +print("Testing URL parsing with fixed regex pattern:") +print("-" * 50) +for url in test_urls: + platform, repo_id = parse_repo_url(url) + result = f"Valid: {platform}, {repo_id}" if platform else "Invalid URL" + print(f"{url} => {result}")