1
Fork 0
mirror of https://github.com/wlinator/luminara.git synced 2024-10-02 18:23:12 +00:00

Add award command and change Help command drastically.

This commit is contained in:
wlinator 2024-03-18 15:11:22 +01:00
parent 258c2f687f
commit 9c363d73c4
13 changed files with 175 additions and 276 deletions

View file

@ -1,6 +1,5 @@
import logging
import mysql.connector
from mysql.connector import Error
from dotenv import load_dotenv
import os

View file

@ -3,7 +3,7 @@ import os
import discord
from dotenv import load_dotenv
from lib.embeds.error import BdayErrors
from lib.embeds.error import BdayErrors, GenericErrors
from services.Birthday import Birthday
from services.GuildConfig import GuildConfig
@ -51,10 +51,9 @@ async def channel(ctx):
async def bot_owner(ctx):
owner_id = os.getenv("OWNER_ID")
if ctx.author.id != int(owner_id):
embed = discord.Embed(description=f"This command requires bot admin permissions.",
color=discord.Color.red())
await ctx.respond(embed=embed, ephemeral=True)
await ctx.respond(embed=GenericErrors.owner_only(ctx), ephemeral=True)
return False
return True

View file

@ -14,6 +14,49 @@ def clean_error_embed(ctx):
return embed
class GenericErrors:
@staticmethod
def default_exception(ctx):
embed = clean_error_embed(ctx)
embed.description += "something went wrong."
embed.set_footer(text="Try the command again", icon_url=exclam_icon)
return embed
@staticmethod
def missing_permissions(ctx):
embed = clean_error_embed(ctx)
embed.description += "you are missing permissions to run this command."
embed.set_footer(text=f"For more info do '{formatter.get_prefix(ctx)}help {formatter.get_invoked_name(ctx)}'",
icon_url=question_icon)
return embed
@staticmethod
def bot_missing_permissions(ctx):
embed = clean_error_embed(ctx)
embed.description += "I can't perform this command because I don't have the required permissions."
embed.set_footer(text=f"For more info do '{formatter.get_prefix(ctx)}help {formatter.get_invoked_name(ctx)}'",
icon_url=question_icon)
return embed
@staticmethod
def command_on_cooldown(ctx, cooldown):
embed = clean_error_embed(ctx)
embed.description += "you are on cooldown."
embed.set_footer(text=f"Try again in {cooldown}", icon_url=exclam_icon)
return embed
@staticmethod
def owner_only(ctx):
embed = clean_error_embed(ctx)
embed.description += "this command requires Racu ownership permissions."
return embed
class EconErrors:
@staticmethod
def missing_bet(ctx):
@ -92,14 +135,6 @@ class EconErrors:
return embed
@staticmethod
def generic_exception(ctx):
embed = clean_error_embed(ctx)
embed.description += "something went wrong."
embed.set_footer(text="Try the command again", icon_url=exclam_icon)
return embed
class BdayErrors:
@staticmethod

23
lib/embeds/greet.py Normal file
View file

@ -0,0 +1,23 @@
import discord
from lib import formatter
question_icon = "https://i.imgur.com/8xccUws.png"
exclam_icon = "https://i.imgur.com/vitwMUu.png"
class Greet:
@staticmethod
def message(member, template=None):
embed = discord.Embed(
color=discord.Color.embed_background(),
description=f"_ _\n**Welcome** to **{member.guild.name}**"
)
if template:
embed.description += "↓↓↓\n" + formatter.template(template, member.name)
embed.set_thumbnail(url=member.display_avatar)
return embed

34
main.py
View file

@ -7,7 +7,8 @@ import discord
from discord.ext import commands, bridge
from dotenv import load_dotenv
from lib import embeds_old
from lib.embeds.error import GenericErrors
from lib.embeds.greet import Greet
from config import json_loader
from handlers.ReactionHandler import ReactionHandler
from handlers.XPHandler import XPHandler
@ -77,7 +78,7 @@ async def on_member_join(member):
):
return
embed = embeds_old.welcome_message(member, config.welcome_message)
embed = Greet.message(member, config.welcome_message)
try:
await member.guild.get_channel(config.welcome_channel_id).send(embed=embed, content=member.mention)
@ -120,31 +121,27 @@ async def on_command_error(ctx, error) -> None:
seconds %= 60
cooldown = "{:02d}:{:02d}".format(int(minutes), int(seconds))
await ctx.respond(
f"⏳ | **{ctx.author.name}** you are on cooldown. "
f"You can use this command again in **{cooldown}**.",
ephemeral=True)
logs.info(f"[CommandHandler] {ctx.author.name} tried to do a command on cooldown.")
await ctx.respond(embed=GenericErrors.command_on_cooldown(ctx, cooldown))
elif isinstance(error, commands.MissingPermissions):
await ctx.respond(strings["error_missing_permissions"].format(ctx.author.name), ephemeral=True)
logs.info(f"[CommandHandler] {ctx.author.name} has missing permissions to do a command: "
f"{ctx.command.qualified_name}")
await ctx.respond(embed=GenericErrors.missing_permissions(ctx))
elif isinstance(error, commands.BotMissingPermissions):
await ctx.respond(strings["error_bot_missing_permissions"].format(ctx.author.name), ephemeral=True)
logs.info(f"[CommandHandler] Racu is missing permissions: {ctx.command.qualified_name}")
await ctx.respond(embed=GenericErrors.bot_missing_permissions(ctx))
elif isinstance(error, discord.CheckFailure) or isinstance(error, commands.CheckFailure):
logs.info(
f"[CommandHandler] {ctx.author.name} tried to do \"/{ctx.command.qualified_name}\" "
f"but a check returned False.")
elif isinstance(error, (discord.CheckFailure, commands.CheckFailure)):
logs.info(f"[CommandHandler] {ctx.author.name} check failure: \"/{ctx.command.qualified_name}\"")
elif isinstance(error, (commands.MissingRequiredArgument, commands.BadArgument)):
# handle locally in module's __init__.py
pass
else:
logs.error(f"[CommandHandler] on_application_command_error: {error}")
await ctx.respond(embed=GenericErrors.default_exception(ctx))
traceback.print_tb(error.original.__traceback__)
logs.error(f"[CommandHandler] on_command_error: {error}")
@client.event
async def on_error(event: str, *args, **kwargs) -> None:
@ -160,6 +157,7 @@ reactions = json_loader.load_reactions()
def load_modules():
modules_list = [
"admin",
"birthdays",
"economy",
"misc"

50
modules/admin/__init__.py Normal file
View file

@ -0,0 +1,50 @@
import logging
import discord
from discord.ext import commands, bridge
import datetime, time
from lib.embeds.info import MiscInfo
from modules.admin import award
from lib.embeds.error import EconErrors
from lib import checks
from main import strings
logs = logging.getLogger('Racu.Core')
class Admin(commands.Cog):
"""
This module is intended for commands that only bot owners can do.
For server configuration with Racu, see the "config" module.
"""
def __init__(self, client):
self.client = client
@bridge.bridge_command(
name="award",
description="Award currency - owner only command.",
help="Awards cash to a specific user. This command can only be performed by a bot administrator.",
guild_only=True
)
@commands.check(checks.channel)
@commands.check(checks.bot_owner)
async def award_command(self, ctx, *, user: discord.User, amount: int):
return await award.cmd(ctx, user, amount)
@award_command.error
async def on_command_error(self, ctx, error):
if isinstance(error, commands.MissingRequiredArgument):
await ctx.respond(embed=EconErrors.missing_bet(ctx))
elif isinstance(error, commands.BadArgument):
await ctx.respond(embed=EconErrors.bad_bet_argument(ctx))
def setup(client):
client.add_cog(Admin(client))

16
modules/admin/award.py Normal file
View file

@ -0,0 +1,16 @@
import discord
from services.Currency import Currency
async def cmd(ctx, user: discord.User, amount: int):
# Currency handler
curr = Currency(user.id)
curr.add_balance(amount)
curr.push()
embed = discord.Embed(
color=discord.Color.green(),
description=f"Awarded **${Currency.format(amount)}** to {user.name}."
)
await ctx.respond(embed=embed)

View file

@ -1,45 +0,0 @@
import json
import logging
import discord
from discord.ext import commands
from dotenv import load_dotenv
from services.Currency import Currency
from lib import checks
load_dotenv('.env')
logs = logging.getLogger('Racu.Core')
with open("config/economy.json") as file:
json_data = json.load(file)
class AwardCog(commands.Cog):
def __init__(self, client):
self.client = client
@commands.slash_command(
name="award",
description="Award currency - owner only command.",
guild_only=True
)
@commands.check(checks.channel)
@commands.check(checks.bot_owner)
async def award(self, ctx, *, user: discord.Option(discord.Member), amount: discord.Option(int)):
# Currency handler
curr = Currency(user.id)
curr.add_balance(amount)
curr.push()
embed = discord.Embed(
color=discord.Color.green(),
description=f"Awarded **${Currency.format(amount)}** to {user.name}."
)
await ctx.respond(embed=embed)
def setup(client):
client.add_cog(AwardCog(client))

View file

@ -1,127 +0,0 @@
import asyncio
import calendar
import datetime
import logging
import random
import discord
from discord import default_permissions
from discord.commands import SlashCommandGroup
from discord.ext import commands, tasks
from config import json_loader
from services.Birthday import Birthday
from services.GuildConfig import GuildConfig
from main import strings
from lib import time, checks
logs = logging.getLogger('Racu.Core')
data = json_loader.load_birthday()
months = data["months"]
messages = data["birthday_messages"]
class BirthdayCog(commands.Cog):
def __init__(self, client):
self.client = client
self.daily_birthday_check.start()
birthday = SlashCommandGroup("birthday", "various birthday commands.", guild_only=True)
@birthday.command(
name="set",
description="Set your birthday."
)
@commands.cooldown(1, 10, commands.BucketType.user)
@commands.check(checks.birthday_module)
async def set_birthday(self, ctx, *,
month: discord.Option(choices=months),
day: discord.Option(int)):
leap_year = 2020
month_index = months.index(month) + 1
max_days = calendar.monthrange(leap_year, month_index)[1]
if not (1 <= day <= max_days):
return await ctx.respond(strings["birthday_invalid_date"].format(ctx.author.name), ephemeral=True)
date_str = f"{leap_year}-{month_index:02d}-{day:02d}"
date_obj = datetime.datetime.strptime(date_str, '%Y-%m-%d')
birthday = Birthday(ctx.author.id, ctx.guild.id)
birthday.set(date_obj)
await ctx.respond(strings["birthday_set"].format(ctx.author.name, month, day), ephemeral=True)
@birthday.command(
name="upcoming",
description="See upcoming birthdays!"
)
@commands.cooldown(1, 10, commands.BucketType.user)
@commands.check(checks.birthday_module)
async def upcoming_birthdays(self, ctx):
upcoming_birthdays = Birthday.get_upcoming_birthdays(ctx.guild.id)
icon = ctx.guild.icon if ctx.guild.icon else "https://i.imgur.com/79XfsbS.png"
embed = discord.Embed(
color=discord.Color.embed_background()
)
embed.set_author(name="Upcoming Birthdays!", icon_url=icon)
embed.set_thumbnail(url="https://i.imgur.com/79XfsbS.png")
for i, (user_id, birthday) in enumerate(upcoming_birthdays, start=1):
try:
member = await ctx.guild.fetch_member(user_id)
name = member.name
except:
name = "Unknown User"
try:
birthday_date = datetime.datetime.strptime(birthday, "%m-%d")
formatted_birthday = birthday_date.strftime("%B %-d")
except ValueError:
# leap year error
formatted_birthday = "February 29"
embed.add_field(
name=f"{name}",
value=f"🎂 {formatted_birthday}",
inline=False
)
await ctx.respond(embed=embed)
@tasks.loop(hours=23, minutes=55)
async def daily_birthday_check(self):
wait_time = time.seconds_until(7, 0)
logs.info(f"[BirthdayHandler] Waiting until 7 AM Eastern for daily check: {round(wait_time)}s")
await asyncio.sleep(wait_time)
embed = discord.Embed(color=discord.Color.embed_background())
embed.set_image(url="https://media1.tenor.com/m/NXvU9jbBUGMAAAAC/fireworks.gif")
for user_id, guild_id in Birthday.get_birthdays_today():
try:
guild = await self.client.fetch_guild(guild_id)
member = await guild.fetch_member(user_id)
guild_config = GuildConfig(guild.id)
if not guild_config.birthday_channel_id:
logs.info(f"[BirthdayHandler] Guild with ID {guild.id} skipped: no birthday channel defined.")
return
message = random.choice(messages)
embed.description = message.format(member.name)
channel = await guild.fetch_channel(guild_config.birthday_channel_id)
await channel.send(embed=embed, content=member.mention)
logs.info(f"[BirthdayHandler] Success! user/guild/channel ID: {member.id}/{guild.id}/{channel.id}")
except Exception as error:
logs.info(f"[BirthdayHandler] Skipped processing user/guild {user_id}/{guild_id}")
# wait one second to avoid rate limits
await asyncio.sleep(1)
def setup(client):
client.add_cog(BirthdayCog(client))

View file

@ -1,54 +0,0 @@
import json
import os
from datetime import datetime, timedelta
from discord.ext import commands
from dotenv import load_dotenv
import lib.time
from services.Dailies import Dailies
from services.Currency import Currency
from main import strings
from lib import checks
load_dotenv('.env')
with open("config/economy.json") as file:
json_data = json.load(file)
class DailyCog(commands.Cog):
def __init__(self, client):
self.client = client
@commands.slash_command(
name="daily",
description="Claim your daily cash!",
guild_only=True
)
@commands.check(checks.channel)
async def daily(self, ctx):
ctx_daily = Dailies(ctx.author.id)
if not ctx_daily.can_be_claimed():
wait_time = datetime.now() + timedelta(seconds=lib.time.seconds_until(7, 0))
unix_time = int(round(wait_time.timestamp()))
return await ctx.respond(content=strings["daily_no_claim"].format(ctx.author.name, unix_time))
ctx_daily.streak = ctx_daily.streak + 1 if ctx_daily.streak_check() else 1
ctx_daily.claimed_at = datetime.now(tz=ctx_daily.tz).isoformat()
ctx_daily.amount = int(100 * (12 * (ctx_daily.streak - 1)))
ctx_daily.refresh()
output = strings["daily_claim"].format(ctx.author.name, Currency.format(ctx_daily.amount))
if ctx_daily.streak > 1:
output += "\n" + strings["daily_streak"].format(ctx_daily.streak)
await ctx.respond(content=output)
def setup(client):
client.add_cog(DailyCog(client))

View file

@ -5,6 +5,7 @@ from discord.ext import commands, bridge
from lib import checks
from lib.embeds.error import EconErrors
from lib.embeds.error import GenericErrors
from modules.economy import leaderboard, blackjack, sell, slots, balance, stats, give, inventory, daily
logs = logging.getLogger('Racu.Core')
@ -29,7 +30,7 @@ class Economy(commands.Cog):
@balance_command.error
async def on_command_error(self, ctx, error):
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error
@bridge.bridge_command(
@ -50,7 +51,7 @@ class Economy(commands.Cog):
elif isinstance(error, commands.BadArgument):
await ctx.respond(embed=EconErrors.bad_bet_argument(ctx))
else:
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error
@bridge.bridge_command(
@ -66,7 +67,7 @@ class Economy(commands.Cog):
@daily_command.error
async def on_command_error(self, ctx, error):
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error
@commands.slash_command(
@ -81,12 +82,13 @@ class Economy(commands.Cog):
@give_command.error
async def on_command_error(self, ctx, error):
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error
@commands.command(
name="give",
help="Give another user some cash. You can use someone's user ID or mention someone."
help="Give another user some cash. You can use someone's user ID or mention someone. The user has to be in the "
"guild you invoke this command in."
)
async def give_command_prefixed(self, ctx, user: discord.User, *, amount: int):
@ -104,7 +106,7 @@ class Economy(commands.Cog):
elif isinstance(error, commands.BadArgument):
await ctx.respond(embed=EconErrors.bad_argument(ctx))
else:
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error
@bridge.bridge_command(
@ -172,7 +174,7 @@ class Economy(commands.Cog):
@stats_command.error
async def on_command_error(self, ctx, error):
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error
@commands.command(
@ -198,7 +200,7 @@ class Economy(commands.Cog):
elif isinstance(error, commands.BadArgument):
await ctx.respond(embed=EconErrors.bad_argument(ctx))
else:
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
raise error

View file

@ -8,6 +8,7 @@ from dotenv import load_dotenv
from handlers.ItemHandler import ItemHandler
from lib import economy_functions, interaction
from lib.embeds.error import EconErrors
from lib.embeds.error import GenericErrors
from main import economy_config
from services.BlackJackStats import BlackJackStats
from services.Currency import Currency
@ -181,7 +182,7 @@ async def cmd(ctx, bet: int):
stats.push()
except Exception as e:
await ctx.respond(embed=EconErrors.generic_exception(ctx))
await ctx.respond(embed=GenericErrors.default_exception(ctx))
logs.error("[CommandHandler] Something went wrong in the gambling command: ", e)
finally:

View file

@ -7,19 +7,20 @@ class RacuHelp(commands.HelpCommand):
return '%s%s %s' % (self.context.clean_prefix, command.qualified_name, command.signature)
def get_command_qualified_name(self, command):
return '%s%s' % (self.context.clean_prefix, command.qualified_name)
return '`%s%s`' % (self.context.clean_prefix, command.qualified_name)
async def send_bot_help(self, mapping):
embed = discord.Embed(title="Help", color=discord.Color.blurple())
embed = discord.Embed(color=discord.Color.blurple())
for cog, commands in mapping.items():
filtered = await self.filter_commands(commands)
if command_signatures := [
self.get_command_qualified_name(c) for c in filtered
self.get_command_qualified_name(c) for c in commands
]:
# Remove duplicates using set() and convert back to a list
unique_command_signatures = list(set(command_signatures))
cog_name = getattr(cog, "qualified_name", "Help")
embed.add_field(name=cog_name, value="\n".join(sorted(unique_command_signatures)), inline=False)
embed.add_field(name=cog_name, value=", ".join(sorted(unique_command_signatures)), inline=False)
channel = self.get_destination()
await channel.send(embed=embed)
@ -29,12 +30,13 @@ class RacuHelp(commands.HelpCommand):
color=discord.Color.blurple())
if command.help:
embed.description = command.help
if alias := command.aliases:
embed.add_field(name="Aliases", value=", ".join(alias), inline=False)
if command.signature:
embed.add_field(name="Usage",
value='%s%s %s' % (self.context.clean_prefix, command.qualified_name, command.signature))
usage_value = '`%s%s %s`' % (self.context.clean_prefix, command.qualified_name, command.signature)
for alias in command.aliases:
usage_value += '\n`%s%s %s`' % (self.context.clean_prefix, alias, command.signature)
embed.add_field(name="Usage", value=usage_value)
channel = self.get_destination()
await channel.send(embed=embed)