mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 18:23:12 +00:00
commit
5c9f1973e9
18 changed files with 335 additions and 246 deletions
|
@ -11,6 +11,7 @@ import services.config_service
|
|||
import services.help_service
|
||||
from lib.constants import CONST
|
||||
from services.blacklist_service import BlacklistUserService
|
||||
from lib.exceptions.LumiExceptions import Blacklisted
|
||||
from db.database import run_migrations
|
||||
|
||||
# Remove the default logger configuration
|
||||
|
@ -42,7 +43,9 @@ client = Client.LumiBot(
|
|||
|
||||
@client.check
|
||||
async def blacklist_check(ctx):
|
||||
return not BlacklistUserService.is_user_blacklisted(ctx.author.id)
|
||||
if BlacklistUserService.is_user_blacklisted(ctx.author.id):
|
||||
raise Blacklisted
|
||||
return True
|
||||
|
||||
|
||||
def load_modules():
|
||||
|
|
|
@ -1,5 +1,39 @@
|
|||
{
|
||||
"lumi_exception_generic": "An error occurred.",
|
||||
"lumi_exception_blacklisted": "User is blacklisted",
|
||||
"admin_award_description": "awarded **${0}** to {1}.",
|
||||
"admin_award_title": "Awarded Currency",
|
||||
"admin_blacklist_author": "User Blacklisted",
|
||||
"admin_blacklist_description": "user `{0}` has been blacklisted from Luminara.",
|
||||
"admin_blacklist_footer": "There is no process to reinstate a blacklisted user. Appeals are not considered.",
|
||||
"admin_sql_inject_description": "```sql\n{0}\n```",
|
||||
"admin_sql_inject_error_description": "```sql\n{0}\n```\n```\n{1}\n```",
|
||||
"admin_sql_inject_error_title": "SQL Query Error",
|
||||
"admin_sql_inject_title": "SQL Query Executed",
|
||||
"admin_sql_select_description": "```sql\nSELECT {0}\n```\n```\n{1}\n```",
|
||||
"admin_sql_select_error_description": "```sql\nSELECT {0}\n```\n```\n{1}\n```",
|
||||
"admin_sql_select_error_title": "SQL Select Query Error",
|
||||
"admin_sql_select_title": "SQL Select Query",
|
||||
"admin_sync_description": "command tree synced successfully.",
|
||||
"admin_sync_error_description": "An error occurred while syncing: {0}",
|
||||
"admin_sync_error_title": "Sync Error",
|
||||
"admin_sync_title": "Sync Successful",
|
||||
"bet_limit": "❌ | **{0}** you cannot place any bets above **${1}**.",
|
||||
"birthday_check_started": "Daily birthday check started.",
|
||||
"birthday_check_skipped": "Birthday announcements in guild with ID {0} skipped: no birthday channel.",
|
||||
"birthday_check_success": "Birthday announcement Success! user/guild/chan ID: {0}/{1}/{2}",
|
||||
"birthday_check_error": "Birthday announcement skipped processing user/guild {0}/{1} | {2}",
|
||||
"birthday_check_finished": "Daily birthday check finished. {0} birthdays processed. {1} birthdays failed.",
|
||||
"birthday_add_invalid_date": "The date you entered is invalid.",
|
||||
"birthday_add_success_author": "Birthday Set",
|
||||
"birthday_add_success_description": "your birthday has been set to **{0} {1}**.",
|
||||
"birthday_delete_success_author": "Birthday Deleted",
|
||||
"birthday_delete_success_description": "your birthday has been deleted from this server.",
|
||||
"birthday_upcoming_author": "Upcoming Birthdays!",
|
||||
"birthday_upcoming_description_line": "🎂 {0} - {1}",
|
||||
"birthday_upcoming_no_birthdays_author": "No Upcoming Birthdays",
|
||||
"birthday_upcoming_no_birthdays": "there are no upcoming birthdays in this server.",
|
||||
"birthday_leap_year": "February 29",
|
||||
"case_case_field": "Case:",
|
||||
"case_case_field_value": "`{0}`",
|
||||
"case_duration_field": "Duration:",
|
||||
|
@ -97,6 +131,7 @@
|
|||
"error_bot_missing_permissions_description": "Lumi lacks the required permissions to run this command.",
|
||||
"error_command_cooldown_author": "Command Cooldown",
|
||||
"error_command_cooldown_description": "try again in **{0:02d}:{1:02d}**.",
|
||||
"error_command_not_found": "No command called \"{0}\" found.",
|
||||
"error_image_url_invalid": "invalid image URL.",
|
||||
"error_invalid_duration": "Invalid duration: {0}",
|
||||
"error_invalid_duration_author": "Invalid Duration",
|
||||
|
@ -150,12 +185,19 @@
|
|||
"mod_banned_user": "user with ID `{0}` has been banned.",
|
||||
"mod_dm_not_sent": "Failed to notify them in DM",
|
||||
"mod_dm_sent": "notified them in DM",
|
||||
"mod_kick_dm": "**{0}** you have been kicked from `{1}`.\n\n**Reason:** `{2}`",
|
||||
"mod_kicked_author": "User Kicked",
|
||||
"mod_kicked_user": "user `{0}` has been kicked.",
|
||||
"mod_no_reason": "No reason provided.",
|
||||
"mod_not_banned": "user with ID `{0}` is not banned.",
|
||||
"mod_not_banned_author": "User Not Banned",
|
||||
"mod_not_timed_out": "user `{0}` is not timed out.",
|
||||
"mod_not_timed_out_author": "User Not Timed Out",
|
||||
"mod_reason": "Moderator: {0} | Reason: {1}",
|
||||
"mod_softban_dm": "**{0}** you have been softbanned from `{1}`.\n\n**Reason:** `{2}`",
|
||||
"mod_softban_unban_reason": "Softban by {0}",
|
||||
"mod_softbanned_author": "User Softbanned",
|
||||
"mod_softbanned_user": "user `{0}` has been softbanned.",
|
||||
"mod_timed_out_author": "User Timed Out",
|
||||
"mod_timed_out_user": "user `{0}` has been timed out.",
|
||||
"mod_timeout_dm": "**{0}** you have been timed out in `{1}` for `{2}`.\n\n**Reason:** `{3}`",
|
||||
|
@ -166,13 +208,6 @@
|
|||
"mod_warn_dm": "**{0}** you have been warned in `{1}`.\n\n**Reason:** `{2}`",
|
||||
"mod_warned_author": "User Warned",
|
||||
"mod_warned_user": "user `{0}` has been warned.",
|
||||
"mod_kicked_author": "User Kicked",
|
||||
"mod_kicked_user": "user `{0}` has been kicked.",
|
||||
"mod_kick_dm": "**{0}** you have been kicked from `{1}`.\n\n**Reason:** `{2}`",
|
||||
"mod_softbanned_author": "User Softbanned",
|
||||
"mod_softban_dm": "**{0}** you have been softbanned from `{1}`.\n\n**Reason:** `{2}`",
|
||||
"mod_softbanned_user": "user `{0}` has been softbanned.",
|
||||
"mod_softban_unban_reason": "Softban by {0}",
|
||||
"ping_author": "I'm online!",
|
||||
"ping_footer": "Latency: {0}ms",
|
||||
"ping_pong": "Pong!",
|
||||
|
@ -194,6 +229,5 @@
|
|||
"xp_lb_field_value": "level: **{0}**\nxp: `{1}/{2}`",
|
||||
"xp_level": "Level {0}",
|
||||
"xp_progress": "Progress to next level",
|
||||
"xp_server_rank": "Server Rank: #{0}",
|
||||
"error_command_not_found": "No command called \"{0}\" found."
|
||||
"xp_server_rank": "Server Rank: #{0}"
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ from lib.exceptions import LumiExceptions
|
|||
|
||||
|
||||
async def on_command_error(ctx, error):
|
||||
if isinstance(error, commands.CommandNotFound):
|
||||
if isinstance(error, (commands.CommandNotFound, LumiExceptions.Blacklisted)):
|
||||
return
|
||||
|
||||
author_text = None
|
||||
|
|
|
@ -85,9 +85,7 @@ class Constants:
|
|||
# birthdays
|
||||
BIRTHDAY_MESSAGES = JsonCache.read_json("birthday")["birthday_messages"]
|
||||
BIRTHDAY_MONTHS = JsonCache.read_json("birthday")["months"]
|
||||
|
||||
|
||||
CONST = Constants()
|
||||
BIRTHDAY_GIF_URL = "https://media1.tenor.com/m/NXvU9jbBUGMAAAAC/fireworks.gif"
|
||||
|
||||
|
||||
CONST = Constants()
|
||||
|
|
|
@ -23,6 +23,7 @@ class EmbedBuilder:
|
|||
timestamp=None,
|
||||
hide_author=False,
|
||||
hide_author_icon=False,
|
||||
hide_timestamp=False,
|
||||
):
|
||||
if not hide_author:
|
||||
if not author_text:
|
||||
|
@ -50,7 +51,8 @@ class EmbedBuilder:
|
|||
url=author_url,
|
||||
)
|
||||
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
|
||||
embed.timestamp = timestamp or datetime.datetime.now()
|
||||
if not hide_timestamp:
|
||||
embed.timestamp = timestamp or datetime.datetime.now()
|
||||
|
||||
if image_url:
|
||||
embed.set_image(url=image_url)
|
||||
|
@ -74,6 +76,7 @@ class EmbedBuilder:
|
|||
timestamp=None,
|
||||
hide_author=False,
|
||||
hide_author_icon=False,
|
||||
hide_timestamp=False,
|
||||
):
|
||||
return EmbedBuilder.create_embed(
|
||||
ctx,
|
||||
|
@ -91,6 +94,7 @@ class EmbedBuilder:
|
|||
timestamp=timestamp,
|
||||
hide_author=hide_author,
|
||||
hide_author_icon=hide_author_icon,
|
||||
hide_timestamp=hide_timestamp,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -108,6 +112,7 @@ class EmbedBuilder:
|
|||
timestamp=None,
|
||||
hide_author=False,
|
||||
hide_author_icon=False,
|
||||
hide_timestamp=False,
|
||||
):
|
||||
return EmbedBuilder.create_embed(
|
||||
ctx,
|
||||
|
@ -125,6 +130,7 @@ class EmbedBuilder:
|
|||
timestamp=timestamp,
|
||||
hide_author=hide_author,
|
||||
hide_author_icon=hide_author_icon,
|
||||
hide_timestamp=hide_timestamp,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -142,6 +148,7 @@ class EmbedBuilder:
|
|||
timestamp=None,
|
||||
hide_author=False,
|
||||
hide_author_icon=False,
|
||||
hide_timestamp=False,
|
||||
):
|
||||
return EmbedBuilder.create_embed(
|
||||
ctx,
|
||||
|
@ -159,6 +166,7 @@ class EmbedBuilder:
|
|||
timestamp=timestamp,
|
||||
hide_author=hide_author,
|
||||
hide_author_icon=hide_author_icon,
|
||||
hide_timestamp=hide_timestamp,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -176,6 +184,7 @@ class EmbedBuilder:
|
|||
timestamp=None,
|
||||
hide_author=False,
|
||||
hide_author_icon=False,
|
||||
hide_timestamp=False,
|
||||
):
|
||||
return EmbedBuilder.create_embed(
|
||||
ctx,
|
||||
|
@ -193,4 +202,5 @@ class EmbedBuilder:
|
|||
timestamp=timestamp,
|
||||
hide_author=hide_author,
|
||||
hide_author_icon=hide_author_icon,
|
||||
hide_timestamp=hide_timestamp,
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from discord.ext import commands
|
||||
from lib.constants import CONST
|
||||
|
||||
|
||||
class BirthdaysDisabled(commands.CheckFailure):
|
||||
|
@ -14,6 +15,16 @@ class LumiException(commands.CommandError):
|
|||
A generic exception to raise for quick error handling.
|
||||
"""
|
||||
|
||||
def __init__(self, message="An error occurred."):
|
||||
def __init__(self, message=CONST.STRINGS["lumi_exception_generic"]):
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class Blacklisted(commands.CommandError):
|
||||
"""
|
||||
Raised when a user is blacklisted.
|
||||
"""
|
||||
|
||||
def __init__(self, message=CONST.STRINGS["lumi_exception_blacklisted"]):
|
||||
self.message = message
|
||||
super().__init__(message)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import discord
|
||||
|
||||
from lib.constants import CONST
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
from services.currency_service import Currency
|
||||
|
||||
|
||||
|
@ -9,9 +10,13 @@ async def cmd(ctx, user: discord.User, amount: int):
|
|||
curr.add_balance(amount)
|
||||
curr.push()
|
||||
|
||||
embed = discord.Embed(
|
||||
color=discord.Color.green(),
|
||||
description=f"Awarded **${Currency.format(amount)}** to {user.name}.",
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["admin_award_title"],
|
||||
description=CONST.STRINGS["admin_award_description"].format(
|
||||
Currency.format(amount),
|
||||
user.name,
|
||||
),
|
||||
)
|
||||
|
||||
await ctx.respond(embed=embed)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
from typing import Optional
|
||||
|
||||
import discord
|
||||
|
||||
from config.parser import JsonCache
|
||||
from lib.constants import CONST
|
||||
from services.blacklist_service import BlacklistUserService
|
||||
|
||||
resources = JsonCache.read_json("art")
|
||||
exclaim_icon = resources["icons"]["exclaim"]
|
||||
hammer_icon = resources["icons"]["hammer"]
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
|
||||
|
||||
async def blacklist_user(
|
||||
|
@ -15,24 +11,15 @@ async def blacklist_user(
|
|||
user: discord.User,
|
||||
reason: Optional[str] = None,
|
||||
) -> None:
|
||||
"""
|
||||
Blacklists a user with an optional reason.
|
||||
|
||||
Args:
|
||||
user_id (int): The ID of the user to blacklist.
|
||||
reason (str, optional): The reason for blacklisting the user. Defaults to "No reason was given".
|
||||
"""
|
||||
blacklist_service = BlacklistUserService(user.id)
|
||||
blacklist_service.add_to_blacklist(reason)
|
||||
|
||||
embed = discord.Embed(
|
||||
description=f"User `{user.name}` has been blacklisted from Luminara.",
|
||||
color=discord.Color.red(),
|
||||
)
|
||||
embed.set_author(name="User Blacklisted", icon_url=hammer_icon)
|
||||
embed.set_footer(
|
||||
text="There is no process to reinstate a blacklisted user. Appeals are not considered.",
|
||||
icon_url=exclaim_icon,
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["admin_blacklist_author"],
|
||||
description=CONST.STRINGS["admin_blacklist_description"].format(user.name),
|
||||
footer_text=CONST.STRINGS["admin_blacklist_footer"],
|
||||
hide_timestamp=True,
|
||||
)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import sqlite3
|
||||
import mysql.connector
|
||||
from lib.constants import CONST
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
from lib.formatter import shorten
|
||||
|
||||
from db import database
|
||||
|
||||
|
@ -9,21 +12,49 @@ async def select_cmd(ctx, query: str):
|
|||
|
||||
try:
|
||||
results = database.select_query(f"SELECT {query}")
|
||||
except sqlite3.Error as error:
|
||||
results = error
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["admin_sql_select_title"],
|
||||
description=CONST.STRINGS["admin_sql_select_description"].format(
|
||||
shorten(query, 200),
|
||||
shorten(str(results), 200),
|
||||
),
|
||||
show_name=False,
|
||||
)
|
||||
except mysql.connector.Error as error:
|
||||
embed = EmbedBuilder.create_error_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["admin_sql_select_error_title"],
|
||||
description=CONST.STRINGS["admin_sql_select_error_description"].format(
|
||||
shorten(query, 200),
|
||||
shorten(str(error), 200),
|
||||
),
|
||||
show_name=False,
|
||||
)
|
||||
|
||||
return await ctx.respond(
|
||||
content=f"```SELECT {query}```\n```{results}```",
|
||||
ephemeral=True,
|
||||
)
|
||||
return await ctx.respond(embed=embed, ephemeral=True)
|
||||
|
||||
|
||||
async def inject_cmd(ctx, query: str):
|
||||
try:
|
||||
database.execute_query(query)
|
||||
await ctx.respond(content=f"That worked!\n```{query}```", ephemeral=True)
|
||||
except sqlite3.Error as error:
|
||||
await ctx.respond(
|
||||
content=f"Query:\n```{query}```\nError message:\n```{error}```",
|
||||
ephemeral=True,
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["admin_sql_inject_title"],
|
||||
description=CONST.STRINGS["admin_sql_inject_description"].format(
|
||||
shorten(query, 200),
|
||||
),
|
||||
show_name=False,
|
||||
)
|
||||
except mysql.connector.Error as error:
|
||||
embed = EmbedBuilder.create_error_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["admin_sql_inject_error_title"],
|
||||
description=CONST.STRINGS["admin_sql_inject_error_description"].format(
|
||||
shorten(query, 200),
|
||||
shorten(str(error), 200),
|
||||
),
|
||||
show_name=False,
|
||||
)
|
||||
|
||||
await ctx.respond(embed=embed, ephemeral=True)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import discord
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
from lib.exceptions.LumiExceptions import LumiException
|
||||
from lib.constants import CONST
|
||||
|
||||
|
||||
async def sync_commands(client, ctx):
|
||||
|
@ -8,9 +9,11 @@ async def sync_commands(client, ctx):
|
|||
await client.sync_commands()
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text="Sync Successful",
|
||||
description="command tree synced successfully.",
|
||||
author_text=CONST.STRINGS["admin_sync_title"],
|
||||
description=CONST.STRINGS["admin_sync_description"],
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
except discord.HTTPException as e:
|
||||
raise LumiException(f"An error occurred while syncing: {e}") from e
|
||||
raise LumiException(
|
||||
CONST.STRINGS["admin_sync_error_description"].format(e),
|
||||
) from e
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
import asyncio
|
||||
import random
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
import discord
|
||||
from discord.commands import SlashCommandGroup
|
||||
from discord.ext import commands, tasks
|
||||
from loguru import logger
|
||||
|
||||
from lib import checks, time
|
||||
from lib import checks
|
||||
from lib.constants import CONST
|
||||
from modules.birthdays import birthday
|
||||
from services.birthday_service import Birthday
|
||||
from services.config_service import GuildConfig
|
||||
from modules.birthdays import birthday, daily_check
|
||||
|
||||
|
||||
class Birthdays(commands.Cog):
|
||||
|
@ -18,9 +14,6 @@ class Birthdays(commands.Cog):
|
|||
self.client = client
|
||||
self.daily_birthday_check.start()
|
||||
|
||||
"""
|
||||
birthday module - slash command only
|
||||
"""
|
||||
birthday = SlashCommandGroup(
|
||||
name="birthday",
|
||||
description="Birthday commands.",
|
||||
|
@ -43,47 +36,9 @@ class Birthdays(commands.Cog):
|
|||
async def upcoming_birthdays(self, ctx):
|
||||
await birthday.upcoming(ctx)
|
||||
|
||||
@tasks.loop(hours=23, minutes=55)
|
||||
@tasks.loop(time=datetime.time(hour=12, minute=0, tzinfo=pytz.UTC)) # 12 PM UTC
|
||||
async def daily_birthday_check(self):
|
||||
wait_time = time.seconds_until(7, 0)
|
||||
logger.debug(
|
||||
f"Waiting until 7 AM Eastern for the daily birthday check: {round(wait_time)}s left.",
|
||||
)
|
||||
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:
|
||||
logger.debug(
|
||||
f"Birthday announcements in guild with ID {guild.id} skipped: no birthday channel.",
|
||||
)
|
||||
return
|
||||
|
||||
message = random.choice(CONST.BIRTHDAY_MESSAGES)
|
||||
embed.description = message.format(member.name)
|
||||
channel = await self.client.get_or_fetch_channel(
|
||||
guild,
|
||||
guild_config.birthday_channel_id,
|
||||
)
|
||||
await channel.send(embed=embed, content=member.mention)
|
||||
logger.debug(
|
||||
f"Birthday announcement Success! user/guild/chan ID: {member.id}/{guild.id}/{channel.id}",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Birthday announcement skipped processing user/guild {user_id}/{guild_id} | {e}",
|
||||
)
|
||||
|
||||
# wait one second to avoid rate limits
|
||||
await asyncio.sleep(1)
|
||||
await daily_check.daily_birthday_check(self.client)
|
||||
|
||||
|
||||
def setup(client):
|
||||
|
|
|
@ -4,7 +4,8 @@ import datetime
|
|||
import discord
|
||||
from discord.ext import commands
|
||||
|
||||
from lib.embeds.info import BdayInfo
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
from lib.constants import CONST
|
||||
from services.birthday_service import Birthday
|
||||
|
||||
|
||||
|
@ -13,59 +14,75 @@ async def add(ctx, month, month_index, day):
|
|||
leap_year = 2020
|
||||
max_days = calendar.monthrange(leap_year, month_index)[1]
|
||||
|
||||
if not (1 <= day <= max_days):
|
||||
raise commands.BadArgument("the date you entered is invalid.")
|
||||
if not 1 <= day <= max_days:
|
||||
raise commands.BadArgument(CONST.STRINGS["birthday_add_invalid_date"])
|
||||
|
||||
date_str = f"{leap_year}-{month_index:02d}-{day:02d}"
|
||||
date_obj = datetime.datetime.strptime(date_str, "%Y-%m-%d")
|
||||
date_obj = datetime.datetime(leap_year, month_index, day)
|
||||
|
||||
birthday = Birthday(ctx.author.id, ctx.guild.id)
|
||||
birthday.set(date_obj)
|
||||
|
||||
await ctx.respond(embed=BdayInfo.set_month(ctx, month, day))
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["birthday_add_success_author"],
|
||||
description=CONST.STRINGS["birthday_add_success_description"].format(
|
||||
month,
|
||||
day,
|
||||
),
|
||||
show_name=True,
|
||||
)
|
||||
await ctx.respond(embed=embed)
|
||||
|
||||
|
||||
async def delete(ctx):
|
||||
"""Delete a user's birthday in a specific server."""
|
||||
birthday = Birthday(ctx.author.id, ctx.guild.id)
|
||||
birthday.delete()
|
||||
await ctx.respond(embed=BdayInfo.delete(ctx))
|
||||
Birthday(ctx.author.id, ctx.guild.id).delete()
|
||||
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["birthday_delete_success_author"],
|
||||
description=CONST.STRINGS["birthday_delete_success_description"],
|
||||
show_name=True,
|
||||
)
|
||||
await ctx.respond(embed=embed)
|
||||
|
||||
|
||||
async def upcoming(ctx):
|
||||
"""Get the upcoming birthdays for a specific server."""
|
||||
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(),
|
||||
if not upcoming_birthdays:
|
||||
embed = EmbedBuilder.create_warning_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["birthday_upcoming_no_birthdays_author"],
|
||||
description=CONST.STRINGS["birthday_upcoming_no_birthdays"],
|
||||
show_name=True,
|
||||
)
|
||||
await ctx.respond(embed=embed)
|
||||
return
|
||||
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx,
|
||||
author_text=CONST.STRINGS["birthday_upcoming_author"],
|
||||
description="",
|
||||
show_name=False,
|
||||
)
|
||||
embed.set_author(name="Upcoming Birthdays!", icon_url=icon)
|
||||
embed.set_thumbnail(url="https://i.imgur.com/79XfsbS.png")
|
||||
embed.set_thumbnail(url=CONST.LUMI_LOGO_TRANSPARENT)
|
||||
|
||||
found_birthdays = 0
|
||||
for user_id, birthday in upcoming_birthdays:
|
||||
birthday_lines = []
|
||||
for user_id, birthday in upcoming_birthdays[:10]:
|
||||
try:
|
||||
member = await ctx.guild.fetch_member(user_id)
|
||||
name = member.name
|
||||
except discord.HTTPException:
|
||||
continue # skip user if not in guild
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
found_birthdays += 1
|
||||
if found_birthdays >= 5:
|
||||
break
|
||||
birthday_lines.append(
|
||||
CONST.STRINGS["birthday_upcoming_description_line"].format(
|
||||
member.name,
|
||||
formatted_birthday,
|
||||
),
|
||||
)
|
||||
except (discord.HTTPException, ValueError):
|
||||
continue
|
||||
|
||||
embed.description = "\n".join(birthday_lines)
|
||||
await ctx.respond(embed=embed)
|
||||
|
|
63
modules/birthdays/daily_check.py
Normal file
63
modules/birthdays/daily_check.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
import asyncio
|
||||
import random
|
||||
from loguru import logger
|
||||
from lib.constants import CONST
|
||||
from services.birthday_service import Birthday
|
||||
from services.config_service import GuildConfig
|
||||
from lib.embed_builder import EmbedBuilder
|
||||
|
||||
|
||||
async def daily_birthday_check(client):
|
||||
logger.info(CONST.STRINGS["birthday_check_started"])
|
||||
birthdays_today = Birthday.get_birthdays_today()
|
||||
processed_birthdays = 0
|
||||
failed_birthdays = 0
|
||||
|
||||
if birthdays_today:
|
||||
for user_id, guild_id in birthdays_today:
|
||||
try:
|
||||
guild = await client.fetch_guild(guild_id)
|
||||
member = await guild.fetch_member(user_id)
|
||||
guild_config = GuildConfig(guild.id)
|
||||
|
||||
if not guild_config.birthday_channel_id:
|
||||
logger.debug(
|
||||
CONST.STRINGS["birthday_check_skipped"].format(guild.id),
|
||||
)
|
||||
continue
|
||||
|
||||
message = random.choice(CONST.BIRTHDAY_MESSAGES)
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
None,
|
||||
author_text="Happy Birthday!",
|
||||
description=message.format(member.name),
|
||||
show_name=False,
|
||||
)
|
||||
embed.set_image(url=CONST.BIRTHDAY_GIF_URL)
|
||||
|
||||
channel = await guild.fetch_channel(guild_config.birthday_channel_id)
|
||||
await channel.send(embed=embed, content=member.mention)
|
||||
logger.debug(
|
||||
CONST.STRINGS["birthday_check_success"].format(
|
||||
member.id,
|
||||
guild.id,
|
||||
channel.id,
|
||||
),
|
||||
)
|
||||
processed_birthdays += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
CONST.STRINGS["birthday_check_error"].format(user_id, guild_id, e),
|
||||
)
|
||||
failed_birthdays += 1
|
||||
|
||||
# wait one second to avoid rate limits
|
||||
await asyncio.sleep(1)
|
||||
|
||||
logger.info(
|
||||
CONST.STRINGS["birthday_check_finished"].format(
|
||||
processed_birthdays,
|
||||
failed_birthdays,
|
||||
),
|
||||
)
|
|
@ -1,8 +1,8 @@
|
|||
import discord
|
||||
from discord.ext.commands import guild_only
|
||||
from discord.ext import bridge, commands
|
||||
from discord.ext.commands import guild_only
|
||||
|
||||
from modules.moderation import ban, cases, warn, timeout, kick, softban
|
||||
from modules.moderation import ban, cases, kick, softban, timeout, warn
|
||||
|
||||
|
||||
class Moderation(commands.Cog):
|
||||
|
@ -28,25 +28,6 @@ class Moderation(commands.Cog):
|
|||
):
|
||||
await ban.ban_user(self, ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="unban",
|
||||
aliases=["ub", "pardon"],
|
||||
description="Unbans a user from the server.",
|
||||
help="Unbans a user from the server, you can use ID or provide their username.",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(ban_members=True)
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@guild_only()
|
||||
async def unban_command(
|
||||
self,
|
||||
ctx,
|
||||
target: discord.User,
|
||||
*,
|
||||
reason: str | None = None,
|
||||
):
|
||||
await ban.unban_user(ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="case",
|
||||
aliases=["c"],
|
||||
|
@ -56,11 +37,7 @@ class Moderation(commands.Cog):
|
|||
)
|
||||
@bridge.has_permissions(view_audit_log=True)
|
||||
@guild_only()
|
||||
async def case_command(
|
||||
self,
|
||||
ctx,
|
||||
case_number: int,
|
||||
):
|
||||
async def case_command(self, ctx, case_number: int):
|
||||
await cases.view_case_by_number(ctx, ctx.guild.id, case_number)
|
||||
|
||||
@bridge.bridge_command(
|
||||
|
@ -75,22 +52,6 @@ class Moderation(commands.Cog):
|
|||
async def cases_command(self, ctx):
|
||||
await cases.view_all_cases_in_guild(ctx, ctx.guild.id)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="modcases",
|
||||
aliases=["moderatorcases", "mc"],
|
||||
description="View all cases by a specific moderator.",
|
||||
help="Lists all moderation cases handled by a specific moderator in the current server.",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(view_audit_log=True)
|
||||
@guild_only()
|
||||
async def moderator_cases_command(
|
||||
self,
|
||||
ctx,
|
||||
moderator: discord.User,
|
||||
):
|
||||
await cases.view_all_cases_by_mod(ctx, ctx.guild.id, moderator)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="editcase",
|
||||
aliases=["uc", "ec"],
|
||||
|
@ -100,32 +61,58 @@ class Moderation(commands.Cog):
|
|||
)
|
||||
@bridge.has_permissions(view_audit_log=True)
|
||||
@guild_only()
|
||||
async def edit_case_command(
|
||||
self,
|
||||
ctx,
|
||||
case_number: int,
|
||||
*,
|
||||
new_reason: str,
|
||||
):
|
||||
async def edit_case_command(self, ctx, case_number: int, *, new_reason: str):
|
||||
await cases.edit_case_reason(ctx, ctx.guild.id, case_number, new_reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="warn",
|
||||
aliases=["w"],
|
||||
description="Warn a user.",
|
||||
help="Warns a user in the server.",
|
||||
name="kick",
|
||||
aliases=["k"],
|
||||
description="Kick a user from the server.",
|
||||
help="Kicks a user from the server.",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(kick_members=True)
|
||||
@commands.bot_has_permissions(kick_members=True)
|
||||
@guild_only()
|
||||
async def warn_command(
|
||||
async def kick_command(
|
||||
self,
|
||||
ctx,
|
||||
target: discord.Member,
|
||||
*,
|
||||
reason: str | None = None,
|
||||
):
|
||||
await warn.warn_user(ctx, target, reason)
|
||||
await kick.kick_user(self, ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="modcases",
|
||||
aliases=["moderatorcases", "mc"],
|
||||
description="View all cases by a specific moderator.",
|
||||
help="Lists all moderation cases handled by a specific moderator in the current server.",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(view_audit_log=True)
|
||||
@guild_only()
|
||||
async def moderator_cases_command(self, ctx, moderator: discord.Member):
|
||||
await cases.view_all_cases_by_mod(ctx, ctx.guild.id, moderator)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="softban",
|
||||
aliases=["sb"],
|
||||
description="Softban a user from the server.",
|
||||
help="Softbans a user from the server (ban and immediately unban to delete messages).",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(ban_members=True)
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@guild_only()
|
||||
async def softban_command(
|
||||
self,
|
||||
ctx,
|
||||
target: discord.Member,
|
||||
*,
|
||||
reason: str | None = None,
|
||||
):
|
||||
await softban.softban_user(ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="timeout",
|
||||
|
@ -147,6 +134,25 @@ class Moderation(commands.Cog):
|
|||
):
|
||||
await timeout.timeout_user(self, ctx, target, duration, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="unban",
|
||||
aliases=["ub", "pardon"],
|
||||
description="Unbans a user from the server.",
|
||||
help="Unbans a user from the server, you can use ID or provide their username.",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(ban_members=True)
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@guild_only()
|
||||
async def unban_command(
|
||||
self,
|
||||
ctx,
|
||||
target: discord.User,
|
||||
*,
|
||||
reason: str | None = None,
|
||||
):
|
||||
await ban.unban_user(ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="untimeout",
|
||||
aliases=["removetimeout", "rto", "uto"],
|
||||
|
@ -167,42 +173,22 @@ class Moderation(commands.Cog):
|
|||
await timeout.untimeout_user(ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="kick",
|
||||
aliases=["k"],
|
||||
description="Kick a user from the server.",
|
||||
help="Kicks a user from the server.",
|
||||
name="warn",
|
||||
aliases=["w"],
|
||||
description="Warn a user.",
|
||||
help="Warns a user in the server.",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(kick_members=True)
|
||||
@commands.bot_has_permissions(kick_members=True)
|
||||
@guild_only()
|
||||
async def kick_command(
|
||||
async def warn_command(
|
||||
self,
|
||||
ctx,
|
||||
target: discord.Member,
|
||||
*,
|
||||
reason: str | None = None,
|
||||
):
|
||||
await kick.kick_user(self, ctx, target, reason)
|
||||
|
||||
@bridge.bridge_command(
|
||||
name="softban",
|
||||
aliases=["sb"],
|
||||
description="Softban a user from the server.",
|
||||
help="Softbans a user from the server (ban and immediately unban to delete messages).",
|
||||
contexts={discord.InteractionContextType.guild},
|
||||
)
|
||||
@bridge.has_permissions(ban_members=True)
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@guild_only()
|
||||
async def softban_command(
|
||||
self,
|
||||
ctx,
|
||||
target: discord.Member,
|
||||
*,
|
||||
reason: str | None = None,
|
||||
):
|
||||
await softban.softban_user(ctx, target, reason)
|
||||
await warn.warn_user(ctx, target, reason)
|
||||
|
||||
|
||||
def setup(client):
|
||||
|
|
|
@ -64,7 +64,7 @@ async def view_all_cases_in_guild(ctx, guild_id: int):
|
|||
await paginator.respond(ctx)
|
||||
|
||||
|
||||
async def view_all_cases_by_mod(ctx, guild_id: int, moderator: discord.User):
|
||||
async def view_all_cases_by_mod(ctx, guild_id: int, moderator: discord.Member):
|
||||
cases = case_service.fetch_cases_by_moderator(guild_id, moderator.id)
|
||||
|
||||
if not cases:
|
||||
|
|
|
@ -58,10 +58,10 @@ async def add_reaction(
|
|||
is_emoji,
|
||||
is_full_match,
|
||||
)
|
||||
await ctx.respond(embed=embed)
|
||||
else:
|
||||
embed = create_failure_embed(trigger_text, is_emoji)
|
||||
await ctx.respond(embed=embed)
|
||||
|
||||
await ctx.respond(embed=embed)
|
||||
|
||||
|
||||
async def check_reaction_limit(
|
||||
|
|
|
@ -38,9 +38,7 @@ class Birthday:
|
|||
tz = pytz.timezone("US/Eastern")
|
||||
today = datetime.datetime.now(tz).strftime("%m-%d")
|
||||
|
||||
birthdays = database.select_query(query, (today,))
|
||||
|
||||
return birthdays
|
||||
return database.select_query(query, (today,))
|
||||
|
||||
@staticmethod
|
||||
def get_upcoming_birthdays(guild_id):
|
||||
|
@ -53,10 +51,4 @@ class Birthday:
|
|||
|
||||
data = database.select_query(query, (guild_id,))
|
||||
|
||||
upcoming = []
|
||||
for row in data:
|
||||
user_id = row[0]
|
||||
birthday = row[1]
|
||||
upcoming.append((user_id, birthday))
|
||||
|
||||
return upcoming
|
||||
return [(row[0], row[1]) for row in data]
|
||||
|
|
|
@ -18,10 +18,10 @@ class LumiHelp(commands.HelpCommand):
|
|||
}
|
||||
|
||||
def get_command_qualified_name(self, command):
|
||||
return "`{}{}`".format(self.context.clean_prefix, command.qualified_name)
|
||||
return f"`{self.context.clean_prefix}{command.qualified_name}`"
|
||||
|
||||
async def send_bot_help(self, mapping):
|
||||
embed = EmbedBuilder.create_info_embed(
|
||||
embed = EmbedBuilder.create_success_embed(
|
||||
ctx=self.context,
|
||||
author_text="Help Command",
|
||||
show_name=False,
|
||||
|
@ -53,17 +53,11 @@ class LumiHelp(commands.HelpCommand):
|
|||
show_name=False,
|
||||
)
|
||||
|
||||
usage_value = "`{}{} {}`".format(
|
||||
self.context.clean_prefix,
|
||||
command.qualified_name,
|
||||
command.signature,
|
||||
usage_value = (
|
||||
f"`{self.context.clean_prefix}{command.qualified_name} {command.signature}`"
|
||||
)
|
||||
for alias in command.aliases:
|
||||
usage_value += "\n`{}{} {}`".format(
|
||||
self.context.clean_prefix,
|
||||
alias,
|
||||
command.signature,
|
||||
)
|
||||
usage_value += f"\n`{self.context.clean_prefix}{alias} {command.signature}`"
|
||||
embed.add_field(name="Usage", value=usage_value, inline=False)
|
||||
|
||||
channel = self.get_destination()
|
||||
|
|
Loading…
Reference in a new issue