Started the command consolodation

This commit is contained in:
Lilac-Rose 2025-11-11 23:47:19 +01:00
parent 6bcf37af3d
commit 8bd385eee5
4 changed files with 329 additions and 107 deletions

View File

@ -158,10 +158,13 @@ class Birthday(commands.Cog):
return filtered[:25] # Discord limit of 25 at a time
@app_commands.command(name="setbirthday", description="Set your birthday timezone.")
# Create command groups
birthday_group = app_commands.Group(name="birthday", description="Manage birthday settings")
@birthday_group.command(name="set", description="Set your birthday and timezone")
@app_commands.describe(date="Your birthday (MM-DD)", timezone="Your timezone, search your city/country to find it!")
@app_commands.autocomplete(timezone=timezone_autocomplete)
async def setbirthday(self, interaction: discord.Interaction, date: str, timezone: str):
async def set_birthday(self, interaction: discord.Interaction, date: str, timezone: str):
try:
datetime.strptime(date, "%m-%d")
except ValueError:
@ -180,8 +183,8 @@ class Birthday(commands.Cog):
await interaction.response.send_message(f"🎂 Birthday set to '{date}' in timezone '{timezone}'!", ephemeral=True)
@app_commands.command(name="removebirthday", description="Remove your saved birthday.")
async def removebirthday(self, interaction: discord.Interaction):
@birthday_group.command(name="remove", description="Remove your saved birthday")
async def remove_birthday(self, interaction: discord.Interaction):
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute("DELETE FROM birthdays WHERE user_id = ?", (interaction.user.id,))
@ -194,29 +197,9 @@ class Birthday(commands.Cog):
else:
await interaction.response.send_message("You don't have a birthday set.", ephemeral=True)
@app_commands.command(name="setbirthdaychannel", description="Set the channel for birthday announcements.")
@app_commands.describe(channel="Channel where birthday announcements will be sent.")
async def setbirthdaychannel(self, interaction: discord.Interaction, channel: discord.TextChannel):
member = interaction.user
if not ModerationBase.is_admin():
await interaction.response.send_message("You do not have permission to use this command.", ephemeral=True)
return
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute("""
INSERT INTO guild_settings (guild_id, channel_id)
VALUES (?, ?)
ON CONFLICT(guild_id) DO UPDATE SET channel_id=excluded.channel_id
""", (interaction.guild.id, channel.id))
conn.commit()
conn.close()
await interaction.response.send_message(f"Birthday announcements will be sent in {channel.mention}")
@app_commands.command(name="listbirthdays", description="List all birthdays for a specific month.")
@birthday_group.command(name="list", description="List all birthdays for a specific month")
@app_commands.describe(month="Specify a month (1-12) to see birthdays for that month")
async def listbirthdays(self, interaction: discord.Interaction, month: int):
async def list_birthdays(self, interaction: discord.Interaction, month: int):
if month < 1 or month > 12:
await interaction.response.send_message("Invalid month! Please use a number between 1 and 12.", ephemeral=True)
return
@ -261,6 +244,24 @@ class Birthday(commands.Cog):
)
await interaction.response.send_message(embed=embed)
@birthday_group.command(name="channel", description="Set the channel for birthday announcements")
@app_commands.describe(channel="Channel where birthday announcements will be sent")
async def set_channel(self, interaction: discord.Interaction, channel: discord.TextChannel):
if not ModerationBase.is_admin():
await interaction.response.send_message("You do not have permission to use this command.", ephemeral=True)
return
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute("""
INSERT INTO guild_settings (guild_id, channel_id)
VALUES (?, ?)
ON CONFLICT(guild_id) DO UPDATE SET channel_id=excluded.channel_id
""", (interaction.guild.id, channel.id))
conn.commit()
conn.close()
await interaction.response.send_message(f"Birthday announcements will be sent in {channel.mention}")
@tasks.loop(minutes=1)
async def check_birthdays(self):

View File

@ -9,8 +9,7 @@ class EmbedColor(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.setup_table()
def get_db(self):
return sqlite3.connect(DB_PATH)
@ -25,8 +24,8 @@ class EmbedColor(commands.Cog):
""")
db.commit()
db.close()
def get_user_color(self, user:discord.User) -> discord.Color:
def get_user_color(self, user: discord.User) -> discord.Color:
db = self.get_db()
cursor = db.cursor()
cursor.execute("SELECT color FROM user_embed_colors WHERE user_id = ?", (user.id,))
@ -35,12 +34,17 @@ class EmbedColor(commands.Cog):
if result and result[0]:
return discord.Color(int(result[0], 16))
return user.accent_color or discord.Color.blurple()
@app_commands.command(name="setembedcolor", description="Set your preferred embed color (hex, e.g. #ff66cc).")
async def setcolor(self, interaction: discord.Interaction, hex_color: str):
# Create a command group
embed_group = app_commands.Group(name="embedcolor", description="Manage your embed color preferences")
@embed_group.command(name="set", description="Set your preferred embed color (hex, e.g. #ff66cc)")
@app_commands.describe(hex_color="Hex color code (e.g., #ff66cc)")
async def set_color(self, interaction: discord.Interaction, hex_color: str):
if not hex_color.startswith("#") or len(hex_color) != 7:
await interaction.response.send_message("Please provide a valid hex color in the format: `#rrggbb`.", ephemeral=True)
return
try:
int(hex_color[1:], 16)
except ValueError:
@ -56,11 +60,11 @@ class EmbedColor(commands.Cog):
""", (interaction.user.id, hex_color[1:]))
db.commit()
db.close()
await interaction.response.send_message(f"Your embed color has been set to `{hex_color}`")
@app_commands.command(name="myembedcolor", description="View your current embed color.")
async def mycolor(self, interaction: discord.Interaction):
@embed_group.command(name="view", description="View your current embed color")
async def view_color(self, interaction: discord.Interaction):
color = self.get_user_color(interaction.user)
hex_code = f"#{color.value:06x}"
embed = discord.Embed(
@ -69,6 +73,20 @@ class EmbedColor(commands.Cog):
color=color
)
await interaction.response.send_message(embed=embed)
@embed_group.command(name="remove", description="Remove your custom embed color")
async def remove_color(self, interaction: discord.Interaction):
db = self.get_db()
cursor = db.cursor()
cursor.execute("DELETE FROM user_embed_colors WHERE user_id = ?", (interaction.user.id,))
changes = db.total_changes
db.commit()
db.close()
if changes > 0:
await interaction.response.send_message("Your custom embed color has been removed. Default colors will be used.", ephemeral=True)
else:
await interaction.response.send_message("You don't have a custom embed color set.", ephemeral=True)
async def setup(bot):
await bot.add_cog(EmbedColor(bot))

191
events/git_webhook.py Normal file
View File

@ -0,0 +1,191 @@
"""
Discord bot cog that runs a webhook server to receive Git commits.
The bot receives the webhook from GitHub and posts directly to Discord.
Add to your .env (optional):
GITHUB_WEBHOOK_SECRET=your_secret_here
WEBHOOK_PORT=5000
"""
import discord
from discord.ext import commands
from aiohttp import web
import hmac
import hashlib
import os
from dotenv import load_dotenv
load_dotenv()
COMMIT_CHANNEL_ID = 876777562599194644
USER_ID_TO_PING = 252130669919076352
WEBHOOK_SECRET = os.getenv("GITHUB_WEBHOOK_SECRET", "")
WEBHOOK_PORT = int(os.getenv("WEBHOOK_PORT", 5000))
class GitWebhook(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.app = web.Application()
self.app.router.add_post('/webhook', self.handle_webhook)
self.app.router.add_get('/health', self.health_check)
self.runner = None
self.site = None
async def cog_load(self):
"""Start the webhook server when cog loads."""
self.runner = web.AppRunner(self.app)
await self.runner.setup()
self.site = web.TCPSite(self.runner, '0.0.0.0', WEBHOOK_PORT)
await self.site.start()
print(f"✅ Git webhook server started on port {WEBHOOK_PORT}")
print(f" Configure GitHub to send webhooks to: http://185.187.170.61:{WEBHOOK_PORT}/webhook")
async def cog_unload(self):
"""Stop the webhook server when cog unloads."""
if self.site:
await self.site.stop()
if self.runner:
await self.runner.cleanup()
print("🛑 Git webhook server stopped")
def verify_signature(self, payload_body, signature_header):
"""Verify GitHub webhook signature for security."""
if not WEBHOOK_SECRET:
return True
hash_object = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
msg=payload_body,
digestmod=hashlib.sha256
)
expected_signature = "sha256=" + hash_object.hexdigest()
return hmac.compare_digest(expected_signature, signature_header)
async def health_check(self, request):
"""Health check endpoint."""
return web.json_response({"status": "healthy"})
async def handle_webhook(self, request):
"""Handle incoming Git webhook from GitHub/GitLab."""
try:
# Verify signature if secret is configured
if WEBHOOK_SECRET:
signature = request.headers.get('X-Hub-Signature-256', '')
body = await request.read()
if not self.verify_signature(body, signature):
return web.json_response({"error": "Invalid signature"}, status=403)
data = await request.json()
else:
data = await request.json()
# Handle GitHub ping event (test from GitHub)
if 'zen' in data and 'hook_id' in data:
print("✅ Received GitHub ping event - webhook is configured correctly!")
return web.json_response({"status": "pong"}, status=200)
# Get the Discord channel
channel = self.bot.get_channel(COMMIT_CHANNEL_ID)
if not channel:
print(f"❌ Channel {COMMIT_CHANNEL_ID} not found!")
return web.json_response({"error": "Channel not found"}, status=500)
# Handle GitHub push events
if 'commits' in data and 'repository' in data:
await self.handle_github_push(data, channel)
return web.json_response({"status": "success"}, status=200)
# Handle GitLab push events
elif 'project' in data and 'commits' in data:
await self.handle_gitlab_push(data, channel)
return web.json_response({"status": "success"}, status=200)
print(f"⚠️ Unknown webhook format. Keys in data: {list(data.keys())}")
return web.json_response({"error": "Unknown webhook format"}, status=400)
except Exception as e:
print(f"❌ Webhook error: {e}")
import traceback
traceback.print_exc()
return web.json_response({"error": str(e)}, status=500)
async def handle_github_push(self, data, channel):
"""Handle GitHub push webhook."""
repo_name = data['repository']['full_name']
repo_url = data['repository']['html_url']
branch = data['ref'].split('/')[-1]
pusher = data['pusher']['name']
commits = data['commits']
compare_url = data.get('compare', '')
if not commits:
return
# Build commit list
commit_lines = []
for commit in commits[:10]: # Show up to 10 commits
short_sha = commit['id'][:7]
message = commit['message'].split('\n')[0] # First line only
if len(message) > 72:
message = message[:69] + "..."
author = commit['author']['name']
url = commit['url']
commit_lines.append(f"[`{short_sha}`]({url}) {message}")
# Create embed
embed = discord.Embed(
title=f"📝 [{repo_name}:{branch}] {len(commits)} new commit{'s' if len(commits) != 1 else ''}",
url=compare_url if compare_url else repo_url,
description="\n".join(commit_lines),
color=discord.Color.blue()
)
if len(commits) > 10:
embed.description += f"\n\n*...and {len(commits) - 10} more commit(s)*"
embed.set_author(name=pusher, icon_url="https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png")
embed.set_footer(text="GitHub")
# Send to Discord
await channel.send(f"<@{USER_ID_TO_PING}>", embed=embed)
async def handle_gitlab_push(self, data, channel):
"""Handle GitLab push webhook."""
repo_name = data['project']['path_with_namespace']
repo_url = data['project']['web_url']
branch = data['ref'].split('/')[-1]
pusher = data['user_name']
commits = data['commits']
if not commits:
return
# Build commit list
commit_lines = []
for commit in commits[:10]:
short_sha = commit['id'][:7]
message = commit['message'].split('\n')[0]
if len(message) > 72:
message = message[:69] + "..."
author = commit['author']['name']
url = commit['url']
commit_lines.append(f"[`{short_sha}`]({url}) {message}")
# Create embed
embed = discord.Embed(
title=f"📝 [{repo_name}:{branch}] {len(commits)} new commit{'s' if len(commits) != 1 else ''}",
url=repo_url,
description="\n".join(commit_lines),
color=0xFC6D26 # GitLab orange
)
if len(commits) > 10:
embed.description += f"\n\n*...and {len(commits) - 10} more commit(s)*"
embed.set_author(name=pusher)
embed.set_footer(text="GitLab")
# Send to Discord
await channel.send(f"<@{USER_ID_TO_PING}>", embed=embed)
async def setup(bot):
await bot.add_cog(GitWebhook(bot))

View File

@ -26,94 +26,106 @@ class CalculateCommand(commands.Cog):
user: discord.Member = None,
board_type: app_commands.Choice[str] = None
):
if user is None:
user = interaction.user
# Immediately defer to prevent timeout
await interaction.response.defer(thinking=True, ephemeral=False)
# Load config fresh
config = load_config()
COOLDOWN = config["COOLDOWN"]
random_xp_config = config.get("RANDOM_XP", {"min": 50, "max": 100})
try:
if user is None:
user = interaction.user
# Determine which database to use
use_lifetime = True if (board_type is None or board_type.value == "lifetime") else False
board_name = "Lifetime" if use_lifetime else "Annual"
# Load config safely
config = load_config()
COOLDOWN = config["COOLDOWN"]
random_xp_config = config.get("RANDOM_XP", {"min": 50, "max": 100})
conn, cur = get_db(lifetime=use_lifetime)
cur.execute("SELECT xp, level, last_message FROM xp WHERE user_id = ?", (str(user.id),))
row = cur.fetchone()
conn.close()
# Determine board type
use_lifetime = True if (board_type is None or board_type.value == "lifetime") else False
board_name = "Lifetime" if use_lifetime else "Annual"
if not row:
await interaction.response.send_message(
f"{user.mention} has no XP yet on the **{board_name}** board.",
ephemeral=True
)
return
current_xp, current_level, last_message = row
# Database query
conn, cur = get_db(db_type="lifetime")
cur.execute("SELECT xp, level, last_message FROM xp WHERE user_id = ?", (str(user.id),))
row = cur.fetchone()
conn.close()
if level <= current_level:
await interaction.response.send_message(
f"{user.mention} is already level {current_level} on the **{board_name}** board. Please choose a higher target level.",
ephemeral=True
)
return
target_xp = xp_for_level(level)
remaining_xp = target_xp - current_xp
if not row:
await interaction.followup.send(
f"{user.mention} has no XP yet on the **{board_name}** board.",
ephemeral=True
)
return
current_xp, current_level, last_message = row
multiplier = get_multiplier(user, apply_multiplier=True)
if level <= current_level:
await interaction.followup.send(
f"{user.mention} is already level {current_level} on the **{board_name}** board. Please choose a higher target level.",
ephemeral=True
)
return
target_xp = xp_for_level(level)
remaining_xp = target_xp - current_xp
min_xp_per_msg = int(random_xp_config["min"] * multiplier)
max_xp_per_msg = int(random_xp_config["max"] * multiplier)
avg_xp_per_msg = (min_xp_per_msg + max_xp_per_msg) / 2
multiplier = get_multiplier(user, apply_multiplier=True)
max_messages = int(remaining_xp / min_xp_per_msg)
min_messages = int(remaining_xp / max_xp_per_msg)
avg_messages = int(remaining_xp / avg_xp_per_msg)
min_xp_per_msg = int(random_xp_config["min"] * multiplier)
max_xp_per_msg = int(random_xp_config["max"] * multiplier)
avg_xp_per_msg = (min_xp_per_msg + max_xp_per_msg) / 2
time_remaining_seconds = avg_messages * COOLDOWN
days = time_remaining_seconds / 86400
max_messages = int(remaining_xp / min_xp_per_msg)
min_messages = int(remaining_xp / max_xp_per_msg)
avg_messages = int(remaining_xp / avg_xp_per_msg)
progress = (current_xp / target_xp) * 100
time_remaining_seconds = avg_messages * COOLDOWN
days = time_remaining_seconds / 86400
bar_length = 30
filled = int((progress / 100) * bar_length)
bar = "" * filled + "" * (bar_length - filled)
progress = (current_xp / target_xp) * 100
current_xp_fmt = f"{current_xp:,}"
target_xp_fmt = f"{target_xp:,}"
remaining_xp_fmt = f"{remaining_xp:,}"
min_messages_fmt = f"{min_messages:,}"
max_messages_fmt = f"{max_messages:,}"
avg_messages_fmt = f"{avg_messages:,}"
# Progress bar
bar_length = 30
filled = int((progress / 100) * bar_length)
bar = "" * filled + "" * (bar_length - filled)
time_since_last = time.time() - last_message
cooldown_ready = can_get_xp(last_message)
cooldown_status = ""
if not cooldown_ready:
cooldown_remaining = COOLDOWN - time_since_last
cooldown_status = f"\n⏳ Cooldown: {int(cooldown_remaining)}s remaining"
# Formatting numbers
current_xp_fmt = f"{current_xp:,}"
target_xp_fmt = f"{target_xp:,}"
remaining_xp_fmt = f"{remaining_xp:,}"
min_messages_fmt = f"{min_messages:,}"
max_messages_fmt = f"{max_messages:,}"
avg_messages_fmt = f"{avg_messages:,}"
response = f"""**{board_name} Level {level} Target**
**Current XP:** {current_xp_fmt} (Level {current_level})
**Target XP:** {target_xp_fmt}
**Remaining XP:** {remaining_xp_fmt}
# Cooldown info
time_since_last = time.time() - last_message
cooldown_ready = can_get_xp(last_message)
cooldown_status = ""
if not cooldown_ready:
cooldown_remaining = COOLDOWN - time_since_last
cooldown_status = f"\n⏳ Cooldown: {int(cooldown_remaining)}s remaining"
**XP per message:** {min_xp_per_msg} - {max_xp_per_msg}
**Messages remaining:** {min_messages_fmt} - {max_messages_fmt} (avg. {avg_messages_fmt})
**Time remaining:** {days:.1f} days{cooldown_status}
response = f"""**{board_name} Level {level} Target**
**Current XP:** {current_xp_fmt} (Level {current_level})
**Target XP:** {target_xp_fmt}
**Remaining XP:** {remaining_xp_fmt}
{bar} ({progress:.2f}%)"""
**XP per message:** {min_xp_per_msg} - {max_xp_per_msg}
**Messages remaining:** {min_messages_fmt} - {max_messages_fmt} (avg. {avg_messages_fmt})
**Time remaining:** {days:.1f} days{cooldown_status}
embed = discord.Embed(
description=response,
color=self.bot.get_cog("EmbedColor").get_user_color(interaction.user)
)
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
{bar} ({progress:.2f}%)"""
await interaction.response.send_message(embed=embed)
# Safe color handling
color_cog = self.bot.get_cog("EmbedColor")
color = color_cog.get_user_color(interaction.user) if color_cog else discord.Color.blurple()
embed = discord.Embed(description=response, color=color)
embed.set_author(name=user.display_name, icon_url=user.display_avatar.url)
await interaction.followup.send(embed=embed)
except Exception as e:
await interaction.followup.send(f"⚠️ **Error:** {e}", ephemeral=False)
async def setup(bot):
await bot.add_cog(CalculateCommand(bot))
await bot.add_cog(CalculateCommand(bot))