This commit is contained in:
Slipstream 2025-05-09 16:42:38 -06:00
parent d95638ee14
commit ee84d5fcec
Signed by: slipstream
GPG Key ID: 13E498CE010AC6FD
6 changed files with 64 additions and 28 deletions

View File

@ -2,7 +2,7 @@ import hashlib
import hmac import hmac
import json import json
import logging import logging
from typing import Dict, Any from typing import Dict, Any, Optional
from fastapi import APIRouter, Request, HTTPException, Depends, Header, Path from fastapi import APIRouter, Request, HTTPException, Depends, Header, Path
import discord # For Color import discord # For Color

View File

@ -16,7 +16,7 @@ class EarningCommands(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = 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): async def daily(self, ctx: commands.Context):
"""Allows users to claim a daily currency reward.""" """Allows users to claim a daily currency reward."""
user_id = ctx.author.id user_id = ctx.author.id
@ -54,7 +54,7 @@ class EarningCommands(commands.Cog):
await ctx.send(embed=embed) 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): async def beg(self, ctx: commands.Context):
"""Allows users to beg for a small amount of currency with a chance of success.""" """Allows users to beg for a small amount of currency with a chance of success."""
user_id = ctx.author.id user_id = ctx.author.id
@ -102,7 +102,7 @@ class EarningCommands(commands.Cog):
) )
await ctx.send(embed=embed) 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): async def work(self, ctx: commands.Context):
"""Allows users to perform work for a small, guaranteed reward.""" """Allows users to perform work for a small, guaranteed reward."""
user_id = ctx.author.id 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) embed.add_field(name="New Balance", value=f"${current_balance:,}", inline=False)
await ctx.send(embed=embed) 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 async def scavenge(self, ctx: commands.Context): # Renamed function
"""Allows users to scavenge for a small chance of finding money.""" """Allows users to scavenge for a small chance of finding money."""
user_id = ctx.author.id user_id = ctx.author.id

View File

@ -975,7 +975,7 @@ class GamesCog(commands.Cog, name="Games"):
# --- Prefix Commands (Legacy Support) --- # --- 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): async def coinflipbet_prefix(self, ctx: commands.Context, opponent: discord.Member):
"""(Prefix) Challenge another user to a coin flip game.""" """(Prefix) Challenge another user to a coin flip game."""
initiator = ctx.author initiator = ctx.author
@ -989,25 +989,25 @@ class GamesCog(commands.Cog, name="Games"):
message = await ctx.send(initial_message, view=view) message = await ctx.send(initial_message, view=view)
view.message = message view.message = message
@commands.command(name="coinflip") @commands.command(name="coinflip", add_to_app_commands=False)
async def coinflip_prefix(self, ctx: commands.Context): async def coinflip_prefix(self, ctx: commands.Context):
"""(Prefix) Flip a coin.""" """(Prefix) Flip a coin."""
result = flip_coin() result = flip_coin()
await ctx.send(f"The coin landed on **{result}**! 🪙") 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): async def roll_prefix(self, ctx: commands.Context):
"""(Prefix) Roll a dice.""" """(Prefix) Roll a dice."""
result = roll_dice() result = roll_dice()
await ctx.send(f"You rolled a **{result}**! 🎲") 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): async def magic8ball_prefix(self, ctx: commands.Context, *, question: str):
"""(Prefix) Ask the magic 8 ball.""" """(Prefix) Ask the magic 8 ball."""
response = magic8ball_response() response = magic8ball_response()
await ctx.send(f"🎱 {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): async def tictactoe_prefix(self, ctx: commands.Context, opponent: discord.Member):
"""(Prefix) Challenge another user to Tic-Tac-Toe.""" """(Prefix) Challenge another user to Tic-Tac-Toe."""
initiator = ctx.author initiator = ctx.author
@ -1021,7 +1021,7 @@ class GamesCog(commands.Cog, name="Games"):
message = await ctx.send(initial_message, view=view) message = await ctx.send(initial_message, view=view)
view.message = message 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"): async def tictactoebot_prefix(self, ctx: commands.Context, difficulty: str = "minimax"):
"""(Prefix) Play Tic-Tac-Toe against the bot.""" """(Prefix) Play Tic-Tac-Toe against the bot."""
difficulty_value = difficulty.lower() difficulty_value = difficulty.lower()
@ -1057,7 +1057,7 @@ class GamesCog(commands.Cog, name="Games"):
) )
view.message = message 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): async def rpschallenge_prefix(self, ctx: commands.Context, opponent: discord.Member):
"""(Prefix) Challenge another user to Rock-Paper-Scissors.""" """(Prefix) Challenge another user to Rock-Paper-Scissors."""
initiator = ctx.author initiator = ctx.author
@ -1071,7 +1071,7 @@ class GamesCog(commands.Cog, name="Games"):
message = await ctx.send(initial_message, view=view) message = await ctx.send(initial_message, view=view)
view.message = message 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): async def rps_prefix(self, ctx: commands.Context, choice: str):
"""(Prefix) Play Rock-Paper-Scissors against the bot.""" """(Prefix) Play Rock-Paper-Scissors against the bot."""
choices = ["Rock", "Paper", "Scissors"] choices = ["Rock", "Paper", "Scissors"]
@ -1099,7 +1099,7 @@ class GamesCog(commands.Cog, name="Games"):
f"{result}" 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): async def chess_prefix(self, ctx: commands.Context, opponent: discord.Member):
"""(Prefix) Start a game of chess with another user.""" """(Prefix) Start a game of chess with another user."""
initiator = ctx.author 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.white_player))
asyncio.create_task(view._send_or_update_dm(view.black_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): async def hangman_prefix(self, ctx: commands.Context):
"""(Prefix) Play a game of Hangman.""" """(Prefix) Play a game of Hangman."""
await play_hangman(self.bot, ctx.channel, ctx.author) 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): async def guess_prefix(self, ctx: commands.Context, guess: int):
"""(Prefix) Guess a number between 1 and 100.""" """(Prefix) Guess a number between 1 and 100."""
number_to_guess = random.randint(1, 100) number_to_guess = random.randint(1, 100)

View File

@ -22,8 +22,8 @@ log = logging.getLogger(__name__)
# Helper to parse repo URL and determine platform # Helper to parse repo URL and determine platform
def parse_repo_url(url: str) -> tuple[Optional[str], Optional[str]]: def parse_repo_url(url: str) -> tuple[Optional[str], Optional[str]]:
"""Parses a Git repository URL to extract platform and a simplified repo identifier.""" """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. # Fixed regex pattern for GitHub URLs
github_match = re.match(r"https^(?:https?://)?(?:www\.)?github\.com/([\w.-]+/[\w.-]+)(?:\.git)?/?$", url) github_match = re.match(r"^(?:https?://)?(?:www\.)?github\.com/([\w.-]+/[\w.-]+)(?:\.git)?/?$", url)
if github_match: if github_match:
return "github", github_match.group(1) return "github", github_match.group(1)
@ -81,7 +81,7 @@ class GitMonitorCog(commands.Cog):
if target_branch: if target_branch:
params["sha"] = target_branch # GitHub uses 'sha' for branch/tag/commit SHA params["sha"] = target_branch # GitHub uses 'sha' for branch/tag/commit SHA
# No 'since_sha' for GitHub commits list. Manual filtering after fetch. # No 'since_sha' for GitHub commits list. Manual filtering after fetch.
async with session.get(api_url, params=params) as response: async with session.get(api_url, params=params) as response:
if response.status == 200: if response.status == 200:
commits_payload = await response.json() commits_payload = await response.json()
@ -91,7 +91,7 @@ class GitMonitorCog(commands.Cog):
temp_new_commits = [] # Clear previous if we found the last one temp_new_commits = [] # Clear previous if we found the last one
continue continue
temp_new_commits.append(commit_item) temp_new_commits.append(commit_item)
if temp_new_commits: if temp_new_commits:
new_commits_data = temp_new_commits new_commits_data = temp_new_commits
latest_fetched_sha = new_commits_data[-1]['sha'] latest_fetched_sha = new_commits_data[-1]['sha']
@ -124,7 +124,7 @@ class GitMonitorCog(commands.Cog):
temp_new_commits = [] temp_new_commits = []
continue continue
temp_new_commits.append(commit_item) temp_new_commits.append(commit_item)
if temp_new_commits: if temp_new_commits:
new_commits_data = temp_new_commits new_commits_data = temp_new_commits
latest_fetched_sha = new_commits_data[-1]['id'] 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" verified_status = "Verified" if verification.get('verified') else "Unverified"
if verification.get('reason') and verification.get('reason') != 'unsigned': if verification.get('reason') and verification.get('reason') != 'unsigned':
verified_status += f" ({verification.get('reason')})" verified_status += f" ({verification.get('reason')})"
# Files changed and stats require another API call per commit: GET /repos/{owner}/{repo}/commits/{sha} # 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. # This is too API intensive for a simple polling loop.
# We will omit detailed file stats for polled GitHub commits for now. # 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="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="Branch", value="default (polling)", inline=True) # Placeholder
embed.add_field(name="Changes", value=files_changed_str, inline=False) embed.add_field(name="Changes", value=files_changed_str, inline=False)
if embed: if embed:
try: try:
await channel.send(embed=embed) 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}") log.error(f"Discord HTTP error sending message for {repo_url}: {dhe}")
else: else:
log.warning(f"Channel {channel_id} not found for guild {guild_id} for repo {repo_url}") log.warning(f"Channel {channel_id} not found for guild {guild_id} for repo {repo_url}")
# Update polling status in DB # 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 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)) 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 # Small delay between processing each repo to be nice to APIs
await asyncio.sleep(2) # 2 seconds delay await asyncio.sleep(2) # 2 seconds delay
@ -365,14 +365,14 @@ class GitMonitorCog(commands.Cog):
return return
embed = discord.Embed(title=f"Monitored Repositories for {interaction.guild.name}", color=discord.Color.blue()) embed = discord.Embed(title=f"Monitored Repositories for {interaction.guild.name}", color=discord.Color.blue())
description_lines = [] description_lines = []
for repo in monitored_repos: for repo in monitored_repos:
channel = self.bot.get_channel(repo['notification_channel_id']) channel = self.bot.get_channel(repo['notification_channel_id'])
channel_mention = channel.mention if channel else f"ID: {repo['notification_channel_id']}" channel_mention = channel.mention if channel else f"ID: {repo['notification_channel_id']}"
method = repo['monitoring_method'].capitalize() method = repo['monitoring_method'].capitalize()
platform = repo['platform'].capitalize() platform = repo['platform'].capitalize()
# Attempt to get a cleaner repo name if possible # Attempt to get a cleaner repo name if possible
_, repo_name_simple = parse_repo_url(repo['repository_url']) _, repo_name_simple = parse_repo_url(repo['repository_url'])
display_name = repo_name_simple if repo_name_simple else 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"- Channel: {channel_mention}\n"
f"- DB ID: `{repo['id']}`" f"- DB ID: `{repo['id']}`"
) )
embed.description = "\n\n".join(description_lines) embed.description = "\n\n".join(description_lines)
if len(embed.description) > 4000 : # Discord embed description limit if len(embed.description) > 4000 : # Discord embed description limit
embed.description = embed.description[:3990] + "\n... (list truncated)" embed.description = embed.description[:3990] + "\n... (list truncated)"

View File

@ -1,3 +1,4 @@
import datetime
import asyncpg import asyncpg
import redis.asyncio as redis import redis.asyncio as redis
import os import os

35
test_url_parser.py Normal file
View File

@ -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}")