mirror of
https://github.com/wlinator/luminara.git
synced 2024-10-02 18:03:12 +00:00
Huge refactor
This commit is contained in:
parent
3c6bc711d6
commit
f87f8a0b39
46 changed files with 853 additions and 516 deletions
|
@ -22,4 +22,4 @@ RUN rm -rf .venv
|
||||||
ENV LANG=en_US.UTF-8
|
ENV LANG=en_US.UTF-8
|
||||||
ENV LC_ALL=en_US.UTF-8
|
ENV LC_ALL=en_US.UTF-8
|
||||||
|
|
||||||
CMD [ "poetry", "run", "python", "-OO", "./main.py" ]
|
CMD [ "poetry", "run", "python", "-O", "./main.py" ]
|
0
db/__init__.py
Normal file
0
db/__init__.py
Normal file
111
db/database.py
111
db/database.py
|
@ -1,6 +1,7 @@
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
import re
|
import re
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import mysql.connector
|
import mysql.connector
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
@ -27,37 +28,33 @@ try:
|
||||||
_cnxpool = create_connection_pool("core-pool", 25)
|
_cnxpool = create_connection_pool("core-pool", 25)
|
||||||
except mysql.connector.Error as e:
|
except mysql.connector.Error as e:
|
||||||
logger.critical(f"Couldn't create the MySQL connection pool: {e}")
|
logger.critical(f"Couldn't create the MySQL connection pool: {e}")
|
||||||
raise e
|
raise
|
||||||
|
|
||||||
|
|
||||||
def execute_query(query, values=None):
|
def execute_query(query: str, values: tuple[Any, ...] | None = None) -> None:
|
||||||
with _cnxpool.get_connection() as conn:
|
with _cnxpool.get_connection() as conn, conn.cursor() as cursor:
|
||||||
with conn.cursor() as cursor:
|
cursor.execute(query, values)
|
||||||
cursor.execute(query, values)
|
conn.commit()
|
||||||
conn.commit()
|
return cursor
|
||||||
return cursor
|
|
||||||
|
|
||||||
|
|
||||||
def select_query(query, values=None):
|
def select_query(query: str, values: tuple[Any, ...] | None = None) -> list[Any]:
|
||||||
with _cnxpool.get_connection() as conn:
|
with _cnxpool.get_connection() as conn, conn.cursor() as cursor:
|
||||||
with conn.cursor() as cursor:
|
cursor.execute(query, values)
|
||||||
cursor.execute(query, values)
|
return cursor.fetchall()
|
||||||
return cursor.fetchall()
|
|
||||||
|
|
||||||
|
|
||||||
def select_query_one(query, values=None):
|
def select_query_one(query: str, values: tuple[Any, ...] | None = None) -> Any | None:
|
||||||
with _cnxpool.get_connection() as conn:
|
with _cnxpool.get_connection() as conn, conn.cursor() as cursor:
|
||||||
with conn.cursor() as cursor:
|
cursor.execute(query, values)
|
||||||
cursor.execute(query, values)
|
output = cursor.fetchone()
|
||||||
output = cursor.fetchone()
|
return output[0] if output else None
|
||||||
return output[0] if output else None
|
|
||||||
|
|
||||||
|
|
||||||
def select_query_dict(query, values=None):
|
def select_query_dict(query: str, values: tuple[Any, ...] | None = None) -> list[dict[str, Any]]:
|
||||||
with _cnxpool.get_connection() as conn:
|
with _cnxpool.get_connection() as conn, conn.cursor(dictionary=True) as cursor:
|
||||||
with conn.cursor(dictionary=True) as cursor:
|
cursor.execute(query, values)
|
||||||
cursor.execute(query, values)
|
return cursor.fetchall()
|
||||||
return cursor.fetchall()
|
|
||||||
|
|
||||||
|
|
||||||
def run_migrations():
|
def run_migrations():
|
||||||
|
@ -66,10 +63,9 @@ def run_migrations():
|
||||||
[f for f in os.listdir(migrations_dir) if f.endswith(".sql")],
|
[f for f in os.listdir(migrations_dir) if f.endswith(".sql")],
|
||||||
)
|
)
|
||||||
|
|
||||||
with _cnxpool.get_connection() as conn:
|
with _cnxpool.get_connection() as conn, conn.cursor() as cursor:
|
||||||
with conn.cursor() as cursor:
|
# Create migrations table if it doesn't exist
|
||||||
# Create migrations table if it doesn't exist
|
cursor.execute("""
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS migrations (
|
CREATE TABLE IF NOT EXISTS migrations (
|
||||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
filename VARCHAR(255) NOT NULL,
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
@ -77,39 +73,38 @@ def run_migrations():
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
for migration_file in migration_files:
|
for migration_file in migration_files:
|
||||||
# Check if migration has already been applied
|
# Check if migration has already been applied
|
||||||
|
cursor.execute(
|
||||||
|
"SELECT COUNT(*) FROM migrations WHERE filename = %s",
|
||||||
|
(migration_file,),
|
||||||
|
)
|
||||||
|
if cursor.fetchone()[0] > 0:
|
||||||
|
logger.debug(
|
||||||
|
f"Migration {migration_file} already applied, skipping.",
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Read and execute migration file
|
||||||
|
migration_sql = pathlib.Path(migrations_dir) / migration_file
|
||||||
|
migration_sql = migration_sql.read_text()
|
||||||
|
try:
|
||||||
|
# Split the migration file into individual statements
|
||||||
|
statements = re.split(r";\s*$", migration_sql, flags=re.MULTILINE)
|
||||||
|
for statement in statements:
|
||||||
|
if statement.strip():
|
||||||
|
cursor.execute(statement)
|
||||||
|
|
||||||
|
# Record successful migration
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT COUNT(*) FROM migrations WHERE filename = %s",
|
"INSERT INTO migrations (filename) VALUES (%s)",
|
||||||
(migration_file,),
|
(migration_file,),
|
||||||
)
|
)
|
||||||
if cursor.fetchone()[0] > 0:
|
conn.commit()
|
||||||
logger.debug(
|
logger.debug(f"Successfully applied migration: {migration_file}")
|
||||||
f"Migration {migration_file} already applied, skipping.",
|
except mysql.connector.Error as e:
|
||||||
)
|
conn.rollback()
|
||||||
continue
|
logger.error(f"Error applying migration {migration_file}: {e}")
|
||||||
|
raise
|
||||||
# Read and execute migration file
|
|
||||||
migration_sql = pathlib.Path(
|
|
||||||
os.path.join(migrations_dir, migration_file),
|
|
||||||
).read_text()
|
|
||||||
try:
|
|
||||||
# Split the migration file into individual statements
|
|
||||||
statements = re.split(r";\s*$", migration_sql, flags=re.MULTILINE)
|
|
||||||
for statement in statements:
|
|
||||||
if statement.strip():
|
|
||||||
cursor.execute(statement)
|
|
||||||
|
|
||||||
# Record successful migration
|
|
||||||
cursor.execute(
|
|
||||||
"INSERT INTO migrations (filename) VALUES (%s)",
|
|
||||||
(migration_file,),
|
|
||||||
)
|
|
||||||
conn.commit()
|
|
||||||
logger.debug(f"Successfully applied migration: {migration_file}")
|
|
||||||
except mysql.connector.Error as e:
|
|
||||||
conn.rollback()
|
|
||||||
logger.error(f"Error applying migration {migration_file}: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
logger.success("All database migrations completed.")
|
logger.success("All database migrations completed.")
|
||||||
|
|
0
db/migrations/__init__.py
Normal file
0
db/migrations/__init__.py
Normal file
0
handlers/__init__.py
Normal file
0
handlers/__init__.py
Normal file
|
@ -1,20 +1,20 @@
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.ext.commands import Cog
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from lib import exceptions
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
from ui.embeds import Builder
|
||||||
from lib import exceptions as LumiExceptions
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_error(
|
async def handle_error(
|
||||||
ctx: commands.Context[commands.Bot],
|
ctx: commands.Context[commands.Bot],
|
||||||
error: commands.CommandError | commands.CheckFailure,
|
error: commands.CommandError | commands.CheckFailure,
|
||||||
) -> None:
|
) -> None:
|
||||||
if isinstance(error, (commands.CommandNotFound, LumiExceptions.Blacklisted)):
|
if isinstance(error, commands.CommandNotFound | exceptions.Blacklisted):
|
||||||
return
|
return
|
||||||
|
|
||||||
author_text = None
|
author_text = None
|
||||||
|
@ -22,11 +22,7 @@ async def handle_error(
|
||||||
footer_text = None
|
footer_text = None
|
||||||
ephemeral = False
|
ephemeral = False
|
||||||
|
|
||||||
if isinstance(error, commands.MissingRequiredArgument):
|
if isinstance(error, commands.MissingRequiredArgument | commands.BadArgument):
|
||||||
author_text = CONST.STRINGS["error_bad_argument_author"]
|
|
||||||
description = CONST.STRINGS["error_bad_argument_description"].format(str(error))
|
|
||||||
|
|
||||||
elif isinstance(error, commands.BadArgument):
|
|
||||||
author_text = CONST.STRINGS["error_bad_argument_author"]
|
author_text = CONST.STRINGS["error_bad_argument_author"]
|
||||||
description = CONST.STRINGS["error_bad_argument_description"].format(str(error))
|
description = CONST.STRINGS["error_bad_argument_description"].format(str(error))
|
||||||
|
|
||||||
|
@ -58,12 +54,12 @@ async def handle_error(
|
||||||
author_text = CONST.STRINGS["error_private_message_only_author"]
|
author_text = CONST.STRINGS["error_private_message_only_author"]
|
||||||
description = CONST.STRINGS["error_private_message_only_description"]
|
description = CONST.STRINGS["error_private_message_only_description"]
|
||||||
|
|
||||||
elif isinstance(error, LumiExceptions.BirthdaysDisabled):
|
elif isinstance(error, exceptions.BirthdaysDisabled):
|
||||||
author_text = CONST.STRINGS["error_birthdays_disabled_author"]
|
author_text = CONST.STRINGS["error_birthdays_disabled_author"]
|
||||||
description = CONST.STRINGS["error_birthdays_disabled_description"]
|
description = CONST.STRINGS["error_birthdays_disabled_description"]
|
||||||
footer_text = CONST.STRINGS["error_birthdays_disabled_footer"]
|
footer_text = CONST.STRINGS["error_birthdays_disabled_footer"]
|
||||||
|
|
||||||
elif isinstance(error, LumiExceptions.LumiException):
|
elif isinstance(error, exceptions.LumiException):
|
||||||
author_text = CONST.STRINGS["error_lumi_exception_author"]
|
author_text = CONST.STRINGS["error_lumi_exception_author"]
|
||||||
description = CONST.STRINGS["error_lumi_exception_description"].format(
|
description = CONST.STRINGS["error_lumi_exception_description"].format(
|
||||||
str(error),
|
str(error),
|
||||||
|
@ -74,7 +70,7 @@ async def handle_error(
|
||||||
description = CONST.STRINGS["error_unknown_error_description"]
|
description = CONST.STRINGS["error_unknown_error_description"]
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=author_text,
|
author_text=author_text,
|
||||||
|
@ -85,7 +81,7 @@ async def handle_error(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def on_error(event: str, *args, **kwargs) -> None:
|
async def on_error(event: str, *args: Any, **kwargs: Any) -> None:
|
||||||
logger.exception(
|
logger.exception(
|
||||||
f"on_error INFO: errors.event.{event} | '*args': {args} | '**kwargs': {kwargs}",
|
f"on_error INFO: errors.event.{event} | '*args': {args} | '**kwargs': {kwargs}",
|
||||||
)
|
)
|
||||||
|
@ -93,20 +89,25 @@ async def on_error(event: str, *args, **kwargs) -> None:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler(Cog):
|
class ErrorHandler(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot) -> None:
|
def __init__(self, bot: commands.Bot) -> None:
|
||||||
self.bot = bot
|
self.bot = bot
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def log_command_error(ctx, error, command_type):
|
async def log_command_error(
|
||||||
log_msg = (
|
ctx: commands.Context[commands.Bot],
|
||||||
f"{ctx.author.name} executed {command_type}{ctx.command.qualified_name}"
|
error: commands.CommandError | commands.CheckFailure,
|
||||||
)
|
command_type: str,
|
||||||
|
) -> None:
|
||||||
|
if ctx.command is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
log_msg = f"{ctx.author.name} executed {command_type}{ctx.command.qualified_name}"
|
||||||
|
|
||||||
log_msg += " in DMs" if ctx.guild is None else f" | guild: {ctx.guild.name} "
|
log_msg += " in DMs" if ctx.guild is None else f" | guild: {ctx.guild.name} "
|
||||||
logger.exception(f"{log_msg} | FAILED: {error}")
|
logger.exception(f"{log_msg} | FAILED: {error}")
|
||||||
|
|
||||||
@Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_command_error(
|
async def on_command_error(
|
||||||
self,
|
self,
|
||||||
ctx: commands.Context[commands.Bot],
|
ctx: commands.Context[commands.Bot],
|
||||||
|
@ -119,7 +120,7 @@ class ErrorHandler(Cog):
|
||||||
logger.exception(f"Error in on_command_error: {e}")
|
logger.exception(f"Error in on_command_error: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_app_command_error(
|
async def on_app_command_error(
|
||||||
self,
|
self,
|
||||||
ctx: commands.Context[commands.Bot],
|
ctx: commands.Context[commands.Bot],
|
||||||
|
@ -132,8 +133,8 @@ class ErrorHandler(Cog):
|
||||||
logger.exception(f"Error in on_app_command_error: {e}")
|
logger.exception(f"Error in on_app_command_error: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
@Cog.listener()
|
@commands.Cog.listener()
|
||||||
async def on_error(self, event: str, *args, **kwargs) -> None:
|
async def on_error(self, event: str, *args: Any, **kwargs: Any) -> None:
|
||||||
await on_error(event, *args, **kwargs)
|
await on_error(event, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
0
lib/__init__.py
Normal file
0
lib/__init__.py
Normal file
|
@ -1,15 +1,18 @@
|
||||||
|
import asyncio
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
import asyncio
|
|
||||||
from lib.loader import CogLoader
|
|
||||||
from db.database import run_migrations
|
from db.database import run_migrations
|
||||||
|
from lib.loader import CogLoader
|
||||||
|
|
||||||
|
|
||||||
class Luminara(commands.Bot):
|
class Luminara(commands.Bot):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args: Any, **kwargs: Any):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.is_shutting_down: bool = False
|
self.is_shutting_down: bool = False
|
||||||
self.setup_task: asyncio.Task = asyncio.create_task(self.setup())
|
self.setup_task: asyncio.Task[None] = asyncio.create_task(self.setup())
|
||||||
self.strip_after_prefix = True
|
self.strip_after_prefix = True
|
||||||
self.case_insensitive = True
|
self.case_insensitive = True
|
||||||
|
|
||||||
|
@ -47,9 +50,7 @@ class Luminara(commands.Bot):
|
||||||
|
|
||||||
await self.close()
|
await self.close()
|
||||||
|
|
||||||
if tasks := [
|
if tasks := [task for task in asyncio.all_tasks() if task is not asyncio.current_task()]:
|
||||||
task for task in asyncio.all_tasks() if task is not asyncio.current_task()
|
|
||||||
]:
|
|
||||||
logger.debug(f"Cancelling {len(tasks)} outstanding tasks.")
|
logger.debug(f"Cancelling {len(tasks)} outstanding tasks.")
|
||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
|
|
171
lib/const.py
171
lib/const.py
|
@ -1,127 +1,132 @@
|
||||||
import os
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
|
from collections.abc import Callable
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Final
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
from functools import lru_cache
|
|
||||||
from typing import Optional, Callable, Set, List, Dict
|
|
||||||
|
|
||||||
|
|
||||||
class _parser:
|
class Parser:
|
||||||
"""Internal parses class. Not intended to be used outside of this module."""
|
"""Internal parses class. Not intended to be used outside of this module."""
|
||||||
|
|
||||||
@lru_cache(maxsize=1024)
|
def __init__(self):
|
||||||
def read_s(self) -> dict:
|
self._cache: dict[str, Any] = {}
|
||||||
return self._read_file("settings.yaml", yaml.safe_load)
|
|
||||||
|
|
||||||
def read_json(self, path: str) -> dict:
|
def read_s(self) -> dict[str, Any]:
|
||||||
return self._read_file(f"locales/{path}.json", json.load)
|
if "settings" not in self._cache:
|
||||||
|
self._cache["settings"] = self._read_file("settings.yaml", yaml.safe_load)
|
||||||
|
return self._cache["settings"]
|
||||||
|
|
||||||
def _read_file(self, file_path: str, load_func: Callable) -> dict:
|
def read_json(self, path: str) -> dict[str, Any]:
|
||||||
with open(file_path) as file:
|
cache_key = f"json_{path}"
|
||||||
|
if cache_key not in self._cache:
|
||||||
|
self._cache[cache_key] = self._read_file(f"locales/{path}.json", json.load)
|
||||||
|
return self._cache[cache_key]
|
||||||
|
|
||||||
|
def _read_file(self, file_path: str, load_func: Callable[[Any], dict[str, Any]]) -> dict[str, Any]:
|
||||||
|
with Path(file_path).open() as file:
|
||||||
return load_func(file)
|
return load_func(file)
|
||||||
|
|
||||||
|
|
||||||
class _constants:
|
class Constants:
|
||||||
_p = _parser()
|
_p: Final = Parser()
|
||||||
_s = _parser().read_s()
|
_s: Final = Parser().read_s()
|
||||||
|
|
||||||
# bot credentials
|
# bot credentials
|
||||||
TOKEN: Optional[str] = os.environ.get("TOKEN")
|
TOKEN: Final[str | None] = os.environ.get("TOKEN")
|
||||||
INSTANCE: Optional[str] = os.environ.get("INSTANCE")
|
INSTANCE: Final[str | None] = os.environ.get("INSTANCE")
|
||||||
XP_GAIN_PER_MESSAGE: int = int(os.environ.get("XP_GAIN_PER_MESSAGE", 1))
|
XP_GAIN_PER_MESSAGE: Final[int] = int(os.environ.get("XP_GAIN_PER_MESSAGE", 1))
|
||||||
XP_GAIN_COOLDOWN: int = int(os.environ.get("XP_GAIN_COOLDOWN", 8))
|
XP_GAIN_COOLDOWN: Final[int] = int(os.environ.get("XP_GAIN_COOLDOWN", 8))
|
||||||
DBX_TOKEN: Optional[str] = os.environ.get("DBX_OAUTH2_REFRESH_TOKEN")
|
DBX_TOKEN: Final[str | None] = os.environ.get("DBX_OAUTH2_REFRESH_TOKEN")
|
||||||
DBX_APP_KEY: Optional[str] = os.environ.get("DBX_APP_KEY")
|
DBX_APP_KEY: Final[str | None] = os.environ.get("DBX_APP_KEY")
|
||||||
DBX_APP_SECRET: Optional[str] = os.environ.get("DBX_APP_SECRET")
|
DBX_APP_SECRET: Final[str | None] = os.environ.get("DBX_APP_SECRET")
|
||||||
MARIADB_USER: Optional[str] = os.environ.get("MARIADB_USER")
|
MARIADB_USER: Final[str | None] = os.environ.get("MARIADB_USER")
|
||||||
MARIADB_PASSWORD: Optional[str] = os.environ.get("MARIADB_PASSWORD")
|
MARIADB_PASSWORD: Final[str | None] = os.environ.get("MARIADB_PASSWORD")
|
||||||
MARIADB_ROOT_PASSWORD: Optional[str] = os.environ.get("MARIADB_ROOT_PASSWORD")
|
MARIADB_ROOT_PASSWORD: Final[str | None] = os.environ.get("MARIADB_ROOT_PASSWORD")
|
||||||
MARIADB_DATABASE: Optional[str] = os.environ.get("MARIADB_DATABASE")
|
MARIADB_DATABASE: Final[str | None] = os.environ.get("MARIADB_DATABASE")
|
||||||
|
|
||||||
OWNER_IDS: Optional[Set[int]] = (
|
OWNER_IDS: Final[set[int] | None] = (
|
||||||
{int(id.strip()) for id in os.environ.get("OWNER_IDS", "").split(",") if id}
|
{int(owner_id.strip()) for owner_id in os.environ.get("OWNER_IDS", "").split(",") if owner_id}
|
||||||
if "OWNER_IDS" in os.environ
|
if "OWNER_IDS" in os.environ
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
# metadata
|
# metadata
|
||||||
TITLE: str = _s["info"]["title"]
|
TITLE: Final[str] = _s["info"]["title"]
|
||||||
AUTHOR: str = _s["info"]["author"]
|
AUTHOR: Final[str] = _s["info"]["author"]
|
||||||
LICENSE: str = _s["info"]["license"]
|
LICENSE: Final[str] = _s["info"]["license"]
|
||||||
VERSION: str = _s["info"]["version"]
|
VERSION: Final[str] = _s["info"]["version"]
|
||||||
REPO_URL: str = _s["info"]["repository_url"]
|
REPO_URL: Final[str] = _s["info"]["repository_url"]
|
||||||
INVITE_URL: str = _s["info"]["invite_url"]
|
INVITE_URL: Final[str] = _s["info"]["invite_url"]
|
||||||
|
|
||||||
# loguru
|
# loguru
|
||||||
LOG_LEVEL: str = _s["logs"]["level"] or "DEBUG"
|
LOG_LEVEL: Final[str] = _s["logs"]["level"] or "DEBUG"
|
||||||
LOG_FORMAT: str = _s["logs"]["format"]
|
LOG_FORMAT: Final[str] = _s["logs"]["format"]
|
||||||
|
|
||||||
# cogs
|
# cogs
|
||||||
COG_IGNORE_LIST: Set[str] = (
|
COG_IGNORE_LIST: Final[set[str]] = set(_s["cogs"]["ignore"]) if _s["cogs"]["ignore"] else set()
|
||||||
set(_s["cogs"]["ignore"]) if _s["cogs"]["ignore"] else set()
|
|
||||||
)
|
|
||||||
|
|
||||||
# images
|
# images
|
||||||
ALLOWED_IMAGE_EXTENSIONS: List[str] = _s["images"]["allowed_image_extensions"]
|
ALLOWED_IMAGE_EXTENSIONS: Final[list[str]] = _s["images"]["allowed_image_extensions"]
|
||||||
BIRTHDAY_GIF_URL: str = _s["images"]["birthday_gif_url"]
|
BIRTHDAY_GIF_URL: Final[str] = _s["images"]["birthday_gif_url"]
|
||||||
|
|
||||||
# colors
|
# colors
|
||||||
COLOR_DEFAULT: int = _s["colors"]["color_default"]
|
COLOR_DEFAULT: Final[int] = _s["colors"]["color_default"]
|
||||||
COLOR_WARNING: int = _s["colors"]["color_warning"]
|
COLOR_WARNING: Final[int] = _s["colors"]["color_warning"]
|
||||||
COLOR_ERROR: int = _s["colors"]["color_error"]
|
COLOR_ERROR: Final[int] = _s["colors"]["color_error"]
|
||||||
|
|
||||||
# economy
|
# economy
|
||||||
DAILY_REWARD: int = _s["economy"]["daily_reward"]
|
DAILY_REWARD: Final[int] = _s["economy"]["daily_reward"]
|
||||||
BLACKJACK_MULTIPLIER: float = _s["economy"]["blackjack_multiplier"]
|
BLACKJACK_MULTIPLIER: Final[float] = _s["economy"]["blackjack_multiplier"]
|
||||||
BLACKJACK_HIT_EMOJI: str = _s["economy"]["blackjack_hit_emoji"]
|
BLACKJACK_HIT_EMOJI: Final[str] = _s["economy"]["blackjack_hit_emoji"]
|
||||||
BLACKJACK_STAND_EMOJI: str = _s["economy"]["blackjack_stand_emoji"]
|
BLACKJACK_STAND_EMOJI: Final[str] = _s["economy"]["blackjack_stand_emoji"]
|
||||||
SLOTS_MULTIPLIERS: Dict[str, float] = _s["economy"]["slots_multipliers"]
|
SLOTS_MULTIPLIERS: Final[dict[str, float]] = _s["economy"]["slots_multipliers"]
|
||||||
|
|
||||||
# art from git repository
|
# art from git repository
|
||||||
_fetch_url: str = _s["art"]["fetch_url"]
|
_fetch_url: Final[str] = _s["art"]["fetch_url"]
|
||||||
|
|
||||||
LUMI_LOGO_OPAQUE: str = _fetch_url + _s["art"]["logo"]["opaque"]
|
LUMI_LOGO_OPAQUE: Final[str] = _fetch_url + _s["art"]["logo"]["opaque"]
|
||||||
LUMI_LOGO_TRANSPARENT: str = _fetch_url + _s["art"]["logo"]["transparent"]
|
LUMI_LOGO_TRANSPARENT: Final[str] = _fetch_url + _s["art"]["logo"]["transparent"]
|
||||||
BOOST_ICON: str = _fetch_url + _s["art"]["icons"]["boost"]
|
BOOST_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["boost"]
|
||||||
CHECK_ICON: str = _fetch_url + _s["art"]["icons"]["check"]
|
CHECK_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["check"]
|
||||||
CROSS_ICON: str = _fetch_url + _s["art"]["icons"]["cross"]
|
CROSS_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["cross"]
|
||||||
EXCLAIM_ICON: str = _fetch_url + _s["art"]["icons"]["exclaim"]
|
EXCLAIM_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["exclaim"]
|
||||||
INFO_ICON: str = _fetch_url + _s["art"]["icons"]["info"]
|
INFO_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["info"]
|
||||||
HAMMER_ICON: str = _fetch_url + _s["art"]["icons"]["hammer"]
|
HAMMER_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["hammer"]
|
||||||
MONEY_BAG_ICON: str = _fetch_url + _s["art"]["icons"]["money_bag"]
|
MONEY_BAG_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["money_bag"]
|
||||||
MONEY_COINS_ICON: str = _fetch_url + _s["art"]["icons"]["money_coins"]
|
MONEY_COINS_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["money_coins"]
|
||||||
QUESTION_ICON: str = _fetch_url + _s["art"]["icons"]["question"]
|
QUESTION_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["question"]
|
||||||
STREAK_ICON: str = _fetch_url + _s["art"]["icons"]["streak"]
|
STREAK_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["streak"]
|
||||||
STREAK_BRONZE_ICON: str = _fetch_url + _s["art"]["icons"]["streak_bronze"]
|
STREAK_BRONZE_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["streak_bronze"]
|
||||||
STREAK_GOLD_ICON: str = _fetch_url + _s["art"]["icons"]["streak_gold"]
|
STREAK_GOLD_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["streak_gold"]
|
||||||
STREAK_SILVER_ICON: str = _fetch_url + _s["art"]["icons"]["streak_silver"]
|
STREAK_SILVER_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["streak_silver"]
|
||||||
WARNING_ICON: str = _fetch_url + _s["art"]["icons"]["warning"]
|
WARNING_ICON: Final[str] = _fetch_url + _s["art"]["icons"]["warning"]
|
||||||
|
|
||||||
# art from imgur
|
# art from imgur
|
||||||
FLOWERS_ART: str = _s["art"]["juicybblue"]["flowers"]
|
FLOWERS_ART: Final[str] = _s["art"]["juicybblue"]["flowers"]
|
||||||
TEAPOT_ART: str = _s["art"]["juicybblue"]["teapot"]
|
TEAPOT_ART: Final[str] = _s["art"]["juicybblue"]["teapot"]
|
||||||
MUFFIN_ART: str = _s["art"]["juicybblue"]["muffin"]
|
MUFFIN_ART: Final[str] = _s["art"]["juicybblue"]["muffin"]
|
||||||
CLOUD_ART: str = _s["art"]["other"]["cloud"]
|
CLOUD_ART: Final[str] = _s["art"]["other"]["cloud"]
|
||||||
TROPHY_ART: str = _s["art"]["other"]["trophy"]
|
TROPHY_ART: Final[str] = _s["art"]["other"]["trophy"]
|
||||||
|
|
||||||
# emotes
|
# emotes
|
||||||
EMOTES_SERVER_ID: int = _s["emotes"]["guild_id"]
|
EMOTES_SERVER_ID: Final[int] = _s["emotes"]["guild_id"]
|
||||||
EMOTE_IDS: Dict[str, int] = _s["emotes"]["emote_ids"]
|
EMOTE_IDS: Final[dict[str, int]] = _s["emotes"]["emote_ids"]
|
||||||
|
|
||||||
# introductions (currently only usable in ONE guild)
|
# introductions (currently only usable in ONE guild)
|
||||||
INTRODUCTIONS_GUILD_ID: int = _s["introductions"]["intro_guild_id"]
|
INTRODUCTIONS_GUILD_ID: Final[int] = _s["introductions"]["intro_guild_id"]
|
||||||
INTRODUCTIONS_CHANNEL_ID: int = _s["introductions"]["intro_channel_id"]
|
INTRODUCTIONS_CHANNEL_ID: Final[int] = _s["introductions"]["intro_channel_id"]
|
||||||
INTRODUCTIONS_QUESTION_MAPPING: Dict[str, str] = _s["introductions"][
|
INTRODUCTIONS_QUESTION_MAPPING: Final[dict[str, str]] = _s["introductions"]["intro_question_mapping"]
|
||||||
"intro_question_mapping"
|
|
||||||
]
|
|
||||||
|
|
||||||
# Reponse strings
|
# Reponse strings
|
||||||
# TODO: Implement switching between languages
|
# TODO: Implement switching between languages
|
||||||
STRINGS = _p.read_json("strings.en-US")
|
STRINGS: Final = _p.read_json("strings.en-US")
|
||||||
LEVEL_MESSAGES = _p.read_json("levels.en-US")
|
LEVEL_MESSAGES: Final = _p.read_json("levels.en-US")
|
||||||
|
|
||||||
_bday = _p.read_json("bdays.en-US")
|
_bday: Final = _p.read_json("bdays.en-US")
|
||||||
BIRTHDAY_MESSAGES = _bday["birthday_messages"]
|
BIRTHDAY_MESSAGES: Final = _bday["birthday_messages"]
|
||||||
BIRTHDAY_MONTHS = _bday["months"]
|
BIRTHDAY_MONTHS: Final = _bday["months"]
|
||||||
|
|
||||||
|
|
||||||
CONST: _constants = _constants()
|
CONST = Constants()
|
||||||
|
|
|
@ -8,15 +8,13 @@ class BirthdaysDisabled(commands.CheckFailure):
|
||||||
Raised when the birthdays module is disabled in ctx.guild.
|
Raised when the birthdays module is disabled in ctx.guild.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class LumiException(commands.CommandError):
|
class LumiException(commands.CommandError):
|
||||||
"""
|
"""
|
||||||
A generic exception to raise for quick error handling.
|
A generic exception to raise for quick error handling.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message=CONST.STRINGS["lumi_exception_generic"]):
|
def __init__(self, message: str = CONST.STRINGS["lumi_exception_generic"]):
|
||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
@ -26,6 +24,6 @@ class Blacklisted(commands.CommandError):
|
||||||
Raised when a user is blacklisted.
|
Raised when a user is blacklisted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, message=CONST.STRINGS["lumi_exception_blacklisted"]):
|
def __init__(self, message: str = CONST.STRINGS["lumi_exception_blacklisted"]):
|
||||||
self.message = message
|
self.message = message
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from loguru import logger
|
|
||||||
from discord.ext import commands
|
|
||||||
from lib.const import CONST
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import aiofiles.os
|
import aiofiles.os
|
||||||
|
from discord.ext import commands
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from lib.const import CONST
|
||||||
|
|
||||||
|
|
||||||
class CogLoader(commands.Cog):
|
class CogLoader(commands.Cog):
|
||||||
|
@ -17,11 +19,7 @@ class CogLoader(commands.Cog):
|
||||||
logger.debug(f"Ignoring cog: {cog_name} because it is in the ignore list")
|
logger.debug(f"Ignoring cog: {cog_name} because it is in the ignore list")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return (
|
return path.suffix == ".py" and not path.name.startswith("_") and await aiofiles.os.path.isfile(path)
|
||||||
path.suffix == ".py"
|
|
||||||
and not path.name.startswith("_")
|
|
||||||
and await aiofiles.os.path.isfile(path)
|
|
||||||
)
|
|
||||||
|
|
||||||
async def load_cogs(self, path: Path) -> None:
|
async def load_cogs(self, path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
|
@ -34,9 +32,7 @@ class CogLoader(commands.Cog):
|
||||||
|
|
||||||
elif await self.is_cog(path):
|
elif await self.is_cog(path):
|
||||||
relative_path: Path = path.relative_to(Path(__file__).parent.parent)
|
relative_path: Path = path.relative_to(Path(__file__).parent.parent)
|
||||||
module: str = (
|
module: str = str(relative_path).replace("/", ".").replace("\\", ".")[:-3]
|
||||||
str(relative_path).replace("/", ".").replace("\\", ".")[:-3]
|
|
||||||
)
|
|
||||||
try:
|
try:
|
||||||
await self.bot.load_extension(name=module)
|
await self.bot.load_extension(name=module)
|
||||||
logger.debug(f"Loaded cog: {module}")
|
logger.debug(f"Loaded cog: {module}")
|
||||||
|
|
|
@ -252,6 +252,13 @@
|
||||||
"ping_footer": "Latency: {0}ms",
|
"ping_footer": "Latency: {0}ms",
|
||||||
"ping_pong": "pong!",
|
"ping_pong": "pong!",
|
||||||
"ping_uptime": "I've been online since <t:{0}:R>.",
|
"ping_uptime": "I've been online since <t:{0}:R>.",
|
||||||
|
"slowmode_author": "Slowmode",
|
||||||
|
"slowmode_channel_not_found": "Channel not found.",
|
||||||
|
"slowmode_duration_not_found": "Please provide a duration in seconds for the slowmode.",
|
||||||
|
"slowmode_footer": "Use 0 to disable slowmode",
|
||||||
|
"slowmode_forbidden": "I don't have permission to change the slowmode in that channel.",
|
||||||
|
"slowmode_invalid_duration": "Slowmode duration must be between 0 and 21600 seconds.",
|
||||||
|
"slowmode_success": "Slowmode set to {0} seconds in {1}.",
|
||||||
"stats_blackjack": "\ud83c\udccf | You've played **{0}** games of BlackJack, betting a total of **${1}**. You won **{2}** of those games with a total payout of **${3}**.",
|
"stats_blackjack": "\ud83c\udccf | You've played **{0}** games of BlackJack, betting a total of **${1}**. You won **{2}** of those games with a total payout of **${3}**.",
|
||||||
"stats_slots": "\ud83c\udfb0 | You've played **{0}** games of Slots, betting a total of **${1}**. Your total payout was **${2}**.",
|
"stats_slots": "\ud83c\udfb0 | You've played **{0}** games of Slots, betting a total of **${1}**. Your total payout was **${2}**.",
|
||||||
"sync_author": "Synced Commands",
|
"sync_author": "Synced Commands",
|
||||||
|
|
6
main.py
6
main.py
|
@ -1,10 +1,12 @@
|
||||||
import sys
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import sys
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from lib.const import CONST
|
|
||||||
from lib.client import Luminara
|
from lib.client import Luminara
|
||||||
|
from lib.const import CONST
|
||||||
|
|
||||||
logger.remove()
|
logger.remove()
|
||||||
logger.add(sys.stdout, format=CONST.LOG_FORMAT, colorize=True, level=CONST.LOG_LEVEL)
|
logger.add(sys.stdout, format=CONST.LOG_FORMAT, colorize=True, level=CONST.LOG_LEVEL)
|
||||||
|
|
0
modules/__init__.py
Normal file
0
modules/__init__.py
Normal file
0
modules/admin/__init__.py
Normal file
0
modules/admin/__init__.py
Normal file
|
@ -1,8 +1,8 @@
|
||||||
from discord.ext import commands
|
|
||||||
import discord
|
import discord
|
||||||
from typing import Optional
|
from discord.ext import commands
|
||||||
from ui.embeds import builder
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
class Sync(commands.Cog):
|
class Sync(commands.Cog):
|
||||||
|
@ -18,7 +18,7 @@ class Sync(commands.Cog):
|
||||||
async def sync(
|
async def sync(
|
||||||
self,
|
self,
|
||||||
ctx: commands.Context[commands.Bot],
|
ctx: commands.Context[commands.Bot],
|
||||||
guild: Optional[discord.Guild] = None,
|
guild: discord.Guild | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not guild:
|
if not guild:
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
@ -28,7 +28,7 @@ class Sync(commands.Cog):
|
||||||
self.bot.tree.copy_global_to(guild=guild)
|
self.bot.tree.copy_global_to(guild=guild)
|
||||||
await self.bot.tree.sync(guild=guild)
|
await self.bot.tree.sync(guild=guild)
|
||||||
|
|
||||||
embed = builder.create_embed(
|
embed = Builder.create_embed(
|
||||||
theme="success",
|
theme="success",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["sync_author"],
|
author_text=CONST.STRINGS["sync_author"],
|
||||||
|
|
0
modules/levels/__init__.py
Normal file
0
modules/levels/__init__.py
Normal file
|
@ -1,8 +1,10 @@
|
||||||
from typing import Optional
|
from typing import cast
|
||||||
|
|
||||||
|
from discord import Embed, Guild, Member
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord import Embed, Guild
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
from ui.embeds import Builder
|
||||||
from ui.views.leaderboard import LeaderboardCommandOptions, LeaderboardCommandView
|
from ui.views.leaderboard import LeaderboardCommandOptions, LeaderboardCommandView
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,17 +18,18 @@ class Leaderboard(commands.Cog):
|
||||||
usage="leaderboard",
|
usage="leaderboard",
|
||||||
)
|
)
|
||||||
async def leaderboard(self, ctx: commands.Context[commands.Bot]) -> None:
|
async def leaderboard(self, ctx: commands.Context[commands.Bot]) -> None:
|
||||||
guild: Optional[Guild] = ctx.guild
|
guild: Guild | None = ctx.guild
|
||||||
if not guild:
|
if not guild:
|
||||||
return
|
return
|
||||||
|
|
||||||
options: LeaderboardCommandOptions = LeaderboardCommandOptions()
|
options: LeaderboardCommandOptions = LeaderboardCommandOptions()
|
||||||
view: LeaderboardCommandView = LeaderboardCommandView(ctx, options)
|
view: LeaderboardCommandView = LeaderboardCommandView(ctx, options)
|
||||||
|
|
||||||
embed: Embed = builder.create_embed(
|
author: Member = cast(Member, ctx.author)
|
||||||
|
embed: Embed = Builder.create_embed(
|
||||||
theme="info",
|
theme="info",
|
||||||
user_name=ctx.author.name,
|
user_name=author.name,
|
||||||
thumbnail_url=ctx.author.display_avatar.url,
|
thumbnail_url=author.display_avatar.url,
|
||||||
hide_name_in_description=True,
|
hide_name_in_description=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from discord.ext import commands
|
|
||||||
from discord import Embed
|
from discord import Embed
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
|
||||||
from services.xp_service import XpService
|
from services.xp_service import XpService
|
||||||
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
class Level(commands.Cog):
|
class Level(commands.Cog):
|
||||||
|
@ -25,7 +26,7 @@ class Level(commands.Cog):
|
||||||
xp_data.level,
|
xp_data.level,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed: Embed = builder.create_embed(
|
embed: Embed = Builder.create_embed(
|
||||||
theme="success",
|
theme="success",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
title=CONST.STRINGS["xp_level"].format(xp_data.level),
|
title=CONST.STRINGS["xp_level"].format(xp_data.level),
|
||||||
|
|
0
modules/misc/__init__.py
Normal file
0
modules/misc/__init__.py
Normal file
|
@ -1,10 +1,9 @@
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from discord.ext import commands
|
|
||||||
from discord.ext.commands import MemberConverter
|
|
||||||
from typing import Optional
|
|
||||||
import discord
|
import discord
|
||||||
from discord import File
|
|
||||||
import httpx
|
import httpx
|
||||||
|
from discord import File
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
|
||||||
async def create_avatar_file(url: str) -> File:
|
async def create_avatar_file(url: str) -> File:
|
||||||
|
@ -42,7 +41,7 @@ class Avatar(commands.Cog):
|
||||||
async def avatar(
|
async def avatar(
|
||||||
self,
|
self,
|
||||||
ctx: commands.Context[commands.Bot],
|
ctx: commands.Context[commands.Bot],
|
||||||
member: Optional[discord.Member] = None,
|
member: discord.Member | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Get the avatar of a member.
|
Get the avatar of a member.
|
||||||
|
@ -55,18 +54,12 @@ class Avatar(commands.Cog):
|
||||||
The member to get the avatar of.
|
The member to get the avatar of.
|
||||||
"""
|
"""
|
||||||
if member is None:
|
if member is None:
|
||||||
member = await MemberConverter().convert(ctx, str(ctx.author.id))
|
member = await commands.MemberConverter().convert(ctx, str(ctx.author.id))
|
||||||
|
|
||||||
guild_avatar: Optional[str] = (
|
guild_avatar: str | None = member.guild_avatar.url if member.guild_avatar else None
|
||||||
member.guild_avatar.url if member.guild_avatar else None
|
profile_avatar: str | None = member.avatar.url if member.avatar else None
|
||||||
)
|
|
||||||
profile_avatar: Optional[str] = member.avatar.url if member.avatar else None
|
|
||||||
|
|
||||||
files: list[File] = [
|
files: list[File] = [await create_avatar_file(avatar) for avatar in [guild_avatar, profile_avatar] if avatar]
|
||||||
await create_avatar_file(avatar)
|
|
||||||
for avatar in [guild_avatar, profile_avatar]
|
|
||||||
if avatar
|
|
||||||
]
|
|
||||||
|
|
||||||
if files:
|
if files:
|
||||||
await ctx.send(files=files)
|
await ctx.send(files=files)
|
||||||
|
|
|
@ -1,20 +1,24 @@
|
||||||
import subprocess
|
import subprocess
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from pathlib import Path
|
||||||
|
|
||||||
|
import dropbox # type: ignore
|
||||||
|
import pytz
|
||||||
from discord.ext import commands, tasks
|
from discord.ext import commands, tasks
|
||||||
import dropbox
|
from dropbox.files import FileMetadata # type: ignore
|
||||||
from dropbox.files import FileMetadata
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
|
|
||||||
|
# Initialize timezone
|
||||||
|
tz = pytz.timezone("America/New_York")
|
||||||
|
|
||||||
# Initialize Dropbox client if instance is "main"
|
# Initialize Dropbox client if instance is "main"
|
||||||
_dbx: Optional[dropbox.Dropbox] = None
|
_dbx: dropbox.Dropbox | None = None
|
||||||
if CONST.INSTANCE and CONST.INSTANCE.lower() == "main":
|
if CONST.INSTANCE and CONST.INSTANCE.lower() == "main":
|
||||||
_app_key: Optional[str] = CONST.DBX_APP_KEY
|
_app_key: str | None = CONST.DBX_APP_KEY
|
||||||
_dbx_token: Optional[str] = CONST.DBX_TOKEN
|
_dbx_token: str | None = CONST.DBX_TOKEN
|
||||||
_app_secret: Optional[str] = CONST.DBX_APP_SECRET
|
_app_secret: str | None = CONST.DBX_APP_SECRET
|
||||||
|
|
||||||
_dbx = dropbox.Dropbox(
|
_dbx = dropbox.Dropbox(
|
||||||
app_key=_app_key,
|
app_key=_app_key,
|
||||||
|
@ -23,36 +27,42 @@ if CONST.INSTANCE and CONST.INSTANCE.lower() == "main":
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def create_db_backup() -> None:
|
def run_db_dump() -> None:
|
||||||
if not _dbx:
|
|
||||||
raise ValueError("Dropbox client is not initialized")
|
|
||||||
|
|
||||||
backup_name: str = datetime.now().strftime("%Y-%m-%d_%H%M") + "_lumi.sql"
|
|
||||||
command: str = (
|
command: str = (
|
||||||
f"mariadb-dump --user={CONST.MARIADB_USER} --password={CONST.MARIADB_PASSWORD} "
|
f"mariadb-dump --user={CONST.MARIADB_USER} --password={CONST.MARIADB_PASSWORD} "
|
||||||
f"--host=db --single-transaction --all-databases > ./db/migrations/100-dump.sql"
|
f"--host=db --single-transaction --all-databases > ./db/migrations/100-dump.sql"
|
||||||
)
|
)
|
||||||
|
|
||||||
subprocess.check_output(command, shell=True)
|
subprocess.check_output(command, shell=True)
|
||||||
|
|
||||||
with open("./db/migrations/100-dump.sql", "rb") as f:
|
|
||||||
_dbx.files_upload(f.read(), f"/{backup_name}")
|
def upload_backup_to_dropbox(backup_name: str) -> None:
|
||||||
|
with Path("./db/migrations/100-dump.sql").open("rb") as f:
|
||||||
|
if _dbx:
|
||||||
|
_dbx.files_upload(f.read(), f"/{backup_name}") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
async def create_db_backup() -> None:
|
||||||
|
if not _dbx:
|
||||||
|
msg = "Dropbox client is not initialized"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
backup_name: str = datetime.now(tz).strftime("%Y-%m-%d_%H%M") + "_lumi.sql"
|
||||||
|
|
||||||
|
run_db_dump()
|
||||||
|
upload_backup_to_dropbox(backup_name)
|
||||||
|
|
||||||
|
|
||||||
async def backup_cleanup() -> None:
|
async def backup_cleanup() -> None:
|
||||||
if not _dbx:
|
if not _dbx:
|
||||||
raise ValueError("Dropbox client is not initialized")
|
msg = "Dropbox client is not initialized"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
result = _dbx.files_list_folder("")
|
result = _dbx.files_list_folder("") # type: ignore
|
||||||
|
|
||||||
all_backup_files: List[str] = [
|
all_backup_files: list[str] = [entry.name for entry in result.entries if isinstance(entry, FileMetadata)] # type: ignore
|
||||||
entry.name
|
|
||||||
for entry in result.entries # type: ignore
|
|
||||||
if isinstance(entry, FileMetadata)
|
|
||||||
]
|
|
||||||
|
|
||||||
for file in sorted(all_backup_files)[:-48]:
|
for file in sorted(all_backup_files)[:-48]:
|
||||||
_dbx.files_delete_v2(f"/{file}")
|
_dbx.files_delete_v2(f"/{file}") # type: ignore
|
||||||
|
|
||||||
|
|
||||||
async def backup() -> None:
|
async def backup() -> None:
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
from discord.ext import commands
|
|
||||||
from lib.const import CONST
|
|
||||||
from ui.embeds import builder
|
|
||||||
import discord
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
import discord
|
||||||
import psutil
|
import psutil
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from lib.const import CONST
|
||||||
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
class Info(commands.Cog):
|
class Info(commands.Cog):
|
||||||
|
@ -29,7 +31,7 @@ class Info(commands.Cog):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
embed: discord.Embed = builder.create_embed(
|
embed: discord.Embed = Builder.create_embed(
|
||||||
theme="info",
|
theme="info",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=f"{CONST.TITLE} v{CONST.VERSION}",
|
author_text=f"{CONST.TITLE} v{CONST.VERSION}",
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import asyncio
|
|
||||||
from typing import Dict, Optional
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
from ui.embeds import Builder
|
||||||
from ui.views.introduction import (
|
from ui.views.introduction import (
|
||||||
IntroductionFinishButtons,
|
IntroductionFinishButtons,
|
||||||
IntroductionStartButtons,
|
IntroductionStartButtons,
|
||||||
|
@ -22,16 +19,14 @@ class Introduction(commands.Cog):
|
||||||
usage="introduction",
|
usage="introduction",
|
||||||
)
|
)
|
||||||
async def introduction(self, ctx: commands.Context[commands.Bot]) -> None:
|
async def introduction(self, ctx: commands.Context[commands.Bot]) -> None:
|
||||||
guild: Optional[discord.Guild] = self.bot.get_guild(
|
guild: discord.Guild | None = self.bot.get_guild(
|
||||||
CONST.INTRODUCTIONS_GUILD_ID,
|
CONST.INTRODUCTIONS_GUILD_ID,
|
||||||
)
|
)
|
||||||
member: Optional[discord.Member] = (
|
member: discord.Member | None = guild.get_member(ctx.author.id) if guild else None
|
||||||
guild.get_member(ctx.author.id) if guild else None
|
|
||||||
)
|
|
||||||
|
|
||||||
if not guild or not member:
|
if not guild or not member:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["intro_no_guild_author"],
|
author_text=CONST.STRINGS["intro_no_guild_author"],
|
||||||
|
@ -41,17 +36,17 @@ class Introduction(commands.Cog):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
question_mapping: Dict[str, str] = CONST.INTRODUCTIONS_QUESTION_MAPPING
|
question_mapping: dict[str, str] = CONST.INTRODUCTIONS_QUESTION_MAPPING
|
||||||
channel: Optional[discord.abc.GuildChannel] = guild.get_channel(
|
channel: discord.abc.GuildChannel | None = guild.get_channel(
|
||||||
CONST.INTRODUCTIONS_CHANNEL_ID,
|
CONST.INTRODUCTIONS_CHANNEL_ID,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not channel or isinstance(
|
if not channel or isinstance(
|
||||||
channel,
|
channel,
|
||||||
(discord.ForumChannel, discord.CategoryChannel),
|
discord.ForumChannel | discord.CategoryChannel,
|
||||||
):
|
):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["intro_no_channel_author"],
|
author_text=CONST.STRINGS["intro_no_channel_author"],
|
||||||
|
@ -61,11 +56,9 @@ class Introduction(commands.Cog):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
view: IntroductionStartButtons | IntroductionFinishButtons = (
|
view: IntroductionStartButtons | IntroductionFinishButtons = IntroductionStartButtons(ctx)
|
||||||
IntroductionStartButtons(ctx)
|
|
||||||
)
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="info",
|
theme="info",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["intro_service_name"],
|
author_text=CONST.STRINGS["intro_service_name"],
|
||||||
|
@ -79,7 +72,7 @@ class Introduction(commands.Cog):
|
||||||
|
|
||||||
if view.clicked_stop:
|
if view.clicked_stop:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["intro_stopped_author"],
|
author_text=CONST.STRINGS["intro_stopped_author"],
|
||||||
|
@ -97,96 +90,95 @@ class Introduction(commands.Cog):
|
||||||
discord.DMChannel,
|
discord.DMChannel,
|
||||||
)
|
)
|
||||||
|
|
||||||
answer_mapping: Dict[str, str] = {}
|
answer_mapping: dict[str, str] = {}
|
||||||
|
|
||||||
for key, question in question_mapping.items():
|
for key, question in question_mapping.items():
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="info",
|
theme="info",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=key,
|
author_text=key,
|
||||||
description=question,
|
description=question,
|
||||||
footer_text=CONST.STRINGS["intro_question_footer"],
|
footer_text=CONST.STRINGS["intro_question_footer"],
|
||||||
),
|
),
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
|
||||||
answer: discord.Message = await self.bot.wait_for(
|
|
||||||
"message",
|
|
||||||
check=check,
|
|
||||||
timeout=300,
|
|
||||||
)
|
)
|
||||||
answer_content: str = answer.content.replace("\n", " ")
|
|
||||||
|
|
||||||
if len(answer_content) > 200:
|
try:
|
||||||
|
answer: discord.Message = await self.bot.wait_for(
|
||||||
|
"message",
|
||||||
|
check=check,
|
||||||
|
timeout=300,
|
||||||
|
)
|
||||||
|
answer_content: str = answer.content.replace("\n", " ")
|
||||||
|
|
||||||
|
if len(answer_content) > 200:
|
||||||
|
await ctx.send(
|
||||||
|
embed=Builder.create_embed(
|
||||||
|
theme="error",
|
||||||
|
user_name=ctx.author.name,
|
||||||
|
author_text=CONST.STRINGS["intro_too_long_author"],
|
||||||
|
description=CONST.STRINGS["intro_too_long"],
|
||||||
|
footer_text=CONST.STRINGS["intro_service_name"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
answer_mapping[key] = answer_content
|
||||||
|
|
||||||
|
except TimeoutError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["intro_too_long_author"],
|
author_text=CONST.STRINGS["intro_timeout_author"],
|
||||||
description=CONST.STRINGS["intro_too_long"],
|
description=CONST.STRINGS["intro_timeout"],
|
||||||
footer_text=CONST.STRINGS["intro_service_name"],
|
footer_text=CONST.STRINGS["intro_service_name"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
answer_mapping[key] = answer_content
|
description: str = "".join(
|
||||||
|
CONST.STRINGS["intro_preview_field"].format(key, value) for key, value in answer_mapping.items()
|
||||||
|
)
|
||||||
|
|
||||||
except asyncio.TimeoutError:
|
preview: discord.Embed = Builder.create_embed(
|
||||||
|
theme="info",
|
||||||
|
user_name=ctx.author.name,
|
||||||
|
author_text=ctx.author.name,
|
||||||
|
author_icon_url=ctx.author.display_avatar.url,
|
||||||
|
description=description,
|
||||||
|
footer_text=CONST.STRINGS["intro_content_footer"],
|
||||||
|
)
|
||||||
|
view = IntroductionFinishButtons(ctx)
|
||||||
|
|
||||||
|
await ctx.send(embed=preview, view=view)
|
||||||
|
await view.wait()
|
||||||
|
|
||||||
|
if view.clicked_confirm:
|
||||||
|
await channel.send(
|
||||||
|
embed=preview,
|
||||||
|
content=CONST.STRINGS["intro_content"].format(ctx.author.mention),
|
||||||
|
)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
|
theme="info",
|
||||||
|
user_name=ctx.author.name,
|
||||||
|
author_text=CONST.STRINGS["intro_post_confirmation_author"],
|
||||||
|
description=CONST.STRINGS["intro_post_confirmation"].format(
|
||||||
|
channel.mention,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await ctx.send(
|
||||||
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["intro_timeout_author"],
|
author_text=CONST.STRINGS["intro_stopped_author"],
|
||||||
description=CONST.STRINGS["intro_timeout"],
|
description=CONST.STRINGS["intro_stopped"],
|
||||||
footer_text=CONST.STRINGS["intro_service_name"],
|
footer_text=CONST.STRINGS["intro_service_name"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
description: str = "".join(
|
|
||||||
CONST.STRINGS["intro_preview_field"].format(key, value)
|
|
||||||
for key, value in answer_mapping.items()
|
|
||||||
)
|
|
||||||
|
|
||||||
preview: discord.Embed = builder.create_embed(
|
|
||||||
theme="info",
|
|
||||||
user_name=ctx.author.name,
|
|
||||||
author_text=ctx.author.name,
|
|
||||||
author_icon_url=ctx.author.display_avatar.url,
|
|
||||||
description=description,
|
|
||||||
footer_text=CONST.STRINGS["intro_content_footer"],
|
|
||||||
)
|
|
||||||
view = IntroductionFinishButtons(ctx)
|
|
||||||
|
|
||||||
await ctx.send(embed=preview, view=view)
|
|
||||||
await view.wait()
|
|
||||||
|
|
||||||
if view.clicked_confirm:
|
|
||||||
await channel.send(
|
|
||||||
embed=preview,
|
|
||||||
content=CONST.STRINGS["intro_content"].format(ctx.author.mention),
|
|
||||||
)
|
|
||||||
await ctx.send(
|
|
||||||
embed=builder.create_embed(
|
|
||||||
theme="info",
|
|
||||||
user_name=ctx.author.name,
|
|
||||||
author_text=CONST.STRINGS["intro_post_confirmation_author"],
|
|
||||||
description=CONST.STRINGS["intro_post_confirmation"].format(
|
|
||||||
channel.mention,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
await ctx.send(
|
|
||||||
embed=builder.create_embed(
|
|
||||||
theme="error",
|
|
||||||
user_name=ctx.author.name,
|
|
||||||
author_text=CONST.STRINGS["intro_stopped_author"],
|
|
||||||
description=CONST.STRINGS["intro_stopped"],
|
|
||||||
footer_text=CONST.STRINGS["intro_service_name"],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot) -> None:
|
async def setup(bot: commands.Bot) -> None:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
from ui.embeds import Builder
|
||||||
from ui.views.invite import InviteButton
|
from ui.views.invite import InviteButton
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ class Invite(commands.Cog):
|
||||||
)
|
)
|
||||||
async def invite(self, ctx: commands.Context[commands.Bot]) -> None:
|
async def invite(self, ctx: commands.Context[commands.Bot]) -> None:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="success",
|
theme="success",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["invite_author"],
|
author_text=CONST.STRINGS["invite_author"],
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
class Ping(commands.Cog):
|
class Ping(commands.Cog):
|
||||||
|
@ -12,7 +13,7 @@ class Ping(commands.Cog):
|
||||||
usage="ping",
|
usage="ping",
|
||||||
)
|
)
|
||||||
async def ping(self, ctx: commands.Context[commands.Bot]) -> None:
|
async def ping(self, ctx: commands.Context[commands.Bot]) -> None:
|
||||||
embed = builder.create_embed(
|
embed = Builder.create_embed(
|
||||||
theme="success",
|
theme="success",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["ping_author"],
|
author_text=CONST.STRINGS["ping_author"],
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
from discord.ext import commands
|
|
||||||
from discord import Embed
|
|
||||||
from lib.const import CONST
|
|
||||||
from ui.embeds import builder
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord import Embed
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from lib.const import CONST
|
||||||
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
class Uptime(commands.Cog):
|
class Uptime(commands.Cog):
|
||||||
def __init__(self, bot: commands.Bot) -> None:
|
def __init__(self, bot: commands.Bot) -> None:
|
||||||
self.bot: commands.Bot = bot
|
self.bot: commands.Bot = bot
|
||||||
self.start_time: datetime = datetime.now()
|
self.start_time: datetime = discord.utils.utcnow()
|
||||||
|
|
||||||
@commands.hybrid_command(
|
@commands.hybrid_command(
|
||||||
name="uptime",
|
name="uptime",
|
||||||
|
@ -17,7 +20,7 @@ class Uptime(commands.Cog):
|
||||||
async def uptime(self, ctx: commands.Context[commands.Bot]) -> None:
|
async def uptime(self, ctx: commands.Context[commands.Bot]) -> None:
|
||||||
unix_timestamp: int = int(self.start_time.timestamp())
|
unix_timestamp: int = int(self.start_time.timestamp())
|
||||||
|
|
||||||
embed: Embed = builder.create_embed(
|
embed: Embed = Builder.create_embed(
|
||||||
theme="info",
|
theme="info",
|
||||||
user_name=ctx.author.name,
|
user_name=ctx.author.name,
|
||||||
author_text=CONST.STRINGS["ping_author"],
|
author_text=CONST.STRINGS["ping_author"],
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from discord.ext import commands
|
|
||||||
from lib.const import CONST
|
|
||||||
from ui.embeds import builder
|
|
||||||
from discord import app_commands
|
|
||||||
import discord
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from lib.const import CONST
|
||||||
|
from ui.embeds import Builder
|
||||||
from wrappers.xkcd import Client, HttpError
|
from wrappers.xkcd import Client, HttpError
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
_xkcd = Client()
|
_xkcd = Client()
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ _xkcd = Client()
|
||||||
async def print_comic(
|
async def print_comic(
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
latest: bool = False,
|
latest: bool = False,
|
||||||
number: Optional[int] = None,
|
number: int | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
if latest:
|
if latest:
|
||||||
|
@ -23,7 +23,7 @@ async def print_comic(
|
||||||
comic = _xkcd.get_random_comic(raw_comic_image=True)
|
comic = _xkcd.get_random_comic(raw_comic_image=True)
|
||||||
|
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="info",
|
theme="info",
|
||||||
author_text=CONST.STRINGS["xkcd_title"].format(comic.id, comic.title),
|
author_text=CONST.STRINGS["xkcd_title"].format(comic.id, comic.title),
|
||||||
description=CONST.STRINGS["xkcd_description"].format(
|
description=CONST.STRINGS["xkcd_description"].format(
|
||||||
|
@ -37,7 +37,7 @@ async def print_comic(
|
||||||
|
|
||||||
except HttpError:
|
except HttpError:
|
||||||
await interaction.response.send_message(
|
await interaction.response.send_message(
|
||||||
embed=builder.create_embed(
|
embed=Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
author_text=CONST.STRINGS["xkcd_not_found_author"],
|
author_text=CONST.STRINGS["xkcd_not_found_author"],
|
||||||
description=CONST.STRINGS["xkcd_not_found"],
|
description=CONST.STRINGS["xkcd_not_found"],
|
||||||
|
@ -61,8 +61,8 @@ class Xkcd(commands.Cog):
|
||||||
await print_comic(interaction)
|
await print_comic(interaction)
|
||||||
|
|
||||||
@xkcd.command(name="search", description="Search for an xkcd comic")
|
@xkcd.command(name="search", description="Search for an xkcd comic")
|
||||||
async def xkcd_search(self, interaction: discord.Interaction, id: int) -> None:
|
async def xkcd_search(self, interaction: discord.Interaction, comic_id: int) -> None:
|
||||||
await print_comic(interaction, number=id)
|
await print_comic(interaction, number=comic_id)
|
||||||
|
|
||||||
|
|
||||||
async def setup(bot: commands.Bot) -> None:
|
async def setup(bot: commands.Bot) -> None:
|
||||||
|
|
0
modules/moderation/__init__.py
Normal file
0
modules/moderation/__init__.py
Normal file
75
modules/moderation/slowmode.py
Normal file
75
modules/moderation/slowmode.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
|
||||||
|
from lib.const import CONST
|
||||||
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
|
class Slowmode(commands.Cog):
|
||||||
|
def __init__(self, bot: commands.Bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
@commands.command(
|
||||||
|
name="slowmode",
|
||||||
|
aliases=["sm"],
|
||||||
|
usage="slowmode <duration> <channel>",
|
||||||
|
)
|
||||||
|
@commands.has_permissions(manage_channels=True)
|
||||||
|
async def slowmode(
|
||||||
|
self,
|
||||||
|
ctx: commands.Context[commands.Bot],
|
||||||
|
arg1: str,
|
||||||
|
arg2: str,
|
||||||
|
) -> None:
|
||||||
|
# define actual channel & duration
|
||||||
|
channel: discord.TextChannel | None = None
|
||||||
|
duration: int | None = None
|
||||||
|
|
||||||
|
for arg in (arg1, arg2):
|
||||||
|
if not channel:
|
||||||
|
try:
|
||||||
|
channel = await commands.TextChannelConverter().convert(ctx, arg)
|
||||||
|
except commands.BadArgument:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
duration = int(arg)
|
||||||
|
else:
|
||||||
|
with contextlib.suppress(ValueError):
|
||||||
|
duration = int(arg)
|
||||||
|
|
||||||
|
if not channel:
|
||||||
|
await ctx.send(CONST.STRINGS["slowmode_channel_not_found"])
|
||||||
|
return
|
||||||
|
|
||||||
|
if duration is None:
|
||||||
|
await ctx.send(CONST.STRINGS["slowmode_duration_not_found"])
|
||||||
|
return
|
||||||
|
|
||||||
|
if duration < 0 or duration > 21600: # 21600 seconds = 6 hours (Discord's max slowmode)
|
||||||
|
await ctx.send("Slowmode duration must be between 0 and 21600 seconds.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
await channel.edit(slowmode_delay=duration)
|
||||||
|
embed = Builder.create_embed(
|
||||||
|
theme="success",
|
||||||
|
user_name=ctx.author.name,
|
||||||
|
author_text=CONST.STRINGS["slowmode_author"],
|
||||||
|
description=CONST.STRINGS["slowmode_success"].format(duration, channel.mention),
|
||||||
|
footer_text=CONST.STRINGS["slowmode_footer"],
|
||||||
|
)
|
||||||
|
except discord.Forbidden:
|
||||||
|
embed = Builder.create_embed(
|
||||||
|
theme="error",
|
||||||
|
user_name=ctx.author.name,
|
||||||
|
author_text=CONST.STRINGS["slowmode_author"],
|
||||||
|
description=CONST.STRINGS["slowmode_forbidden"],
|
||||||
|
footer_text=CONST.STRINGS["slowmode_footer"],
|
||||||
|
)
|
||||||
|
|
||||||
|
await ctx.send(embed=embed)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup(bot: commands.Bot) -> None:
|
||||||
|
await bot.add_cog(Slowmode(bot))
|
142
poetry.lock
generated
142
poetry.lock
generated
|
@ -174,6 +174,17 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
frozenlist = ">=1.1.0"
|
frozenlist = ">=1.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
description = "Reusable constraint types to use with typing.Annotated"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
|
||||||
|
{file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "4.4.0"
|
version = "4.4.0"
|
||||||
|
@ -820,15 +831,138 @@ files = [
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
|
test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.8.2"
|
||||||
|
description = "Data validation using Python type hints"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8"},
|
||||||
|
{file = "pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
annotated-types = ">=0.4.0"
|
||||||
|
pydantic-core = "2.20.1"
|
||||||
|
typing-extensions = [
|
||||||
|
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
|
||||||
|
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
email = ["email-validator (>=2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.20.1"
|
||||||
|
description = "Core functionality for Pydantic validation and serialization"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-none-win32.whl", hash = "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp38-none-win_amd64.whl", hash = "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2"},
|
||||||
|
{file = "pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a"},
|
||||||
|
{file = "pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7"},
|
||||||
|
{file = "pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyright"
|
name = "pyright"
|
||||||
version = "1.1.377"
|
version = "1.1.378"
|
||||||
description = "Command line wrapper for pyright"
|
description = "Command line wrapper for pyright"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "pyright-1.1.377-py3-none-any.whl", hash = "sha256:af0dd2b6b636c383a6569a083f8c5a8748ae4dcde5df7914b3f3f267e14dd162"},
|
{file = "pyright-1.1.378-py3-none-any.whl", hash = "sha256:8853776138b01bc284da07ac481235be7cc89d3176b073d2dba73636cb95be79"},
|
||||||
{file = "pyright-1.1.377.tar.gz", hash = "sha256:aabc30fedce0ded34baa0c49b24f10e68f4bfc8f68ae7f3d175c4b0f256b4fcf"},
|
{file = "pyright-1.1.378.tar.gz", hash = "sha256:78a043be2876d12d0af101d667e92c7734f3ebb9db71dccc2c220e7e7eb89ca2"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -1165,4 +1299,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "1bec4428d16328dd4054cda20654e446c54aa0463b79ef32ae4cfa10de7c0dfd"
|
content-hash = "e6ab702cf6efc2ec25a9c033029869f9c4c3631e2e6063c4241153ea8c1f8e79"
|
||||||
|
|
137
pyproject.toml
137
pyproject.toml
|
@ -8,23 +8,148 @@ readme = "README.md"
|
||||||
version = "3"
|
version = "3"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
|
aiocache = "^0.12.2"
|
||||||
|
aioconsole = "^0.7.1"
|
||||||
|
aiofiles = "^24.1.0"
|
||||||
discord-py = "^2.4.0"
|
discord-py = "^2.4.0"
|
||||||
|
dropbox = "^12.0.2"
|
||||||
httpx = "^0.27.2"
|
httpx = "^0.27.2"
|
||||||
loguru = "^0.7.2"
|
loguru = "^0.7.2"
|
||||||
mysql-connector-python = "^9.0.0"
|
mysql-connector-python = "^9.0.0"
|
||||||
pre-commit = "^3.8.0"
|
pre-commit = "^3.8.0"
|
||||||
|
psutil = "^6.0.0"
|
||||||
pyright = "^1.1.377"
|
pyright = "^1.1.377"
|
||||||
python = "^3.12"
|
python = "^3.12"
|
||||||
|
pytz = "^2024.1"
|
||||||
pyyaml = "^6.0.2"
|
pyyaml = "^6.0.2"
|
||||||
ruff = "^0.6.2"
|
ruff = "^0.6.2"
|
||||||
typing-extensions = "^4.12.2"
|
typing-extensions = "^4.12.2"
|
||||||
aiofiles = "^24.1.0"
|
pydantic = "^2.8.2"
|
||||||
aiocache = "^0.12.2"
|
|
||||||
aioconsole = "^0.7.1"
|
|
||||||
psutil = "^6.0.0"
|
|
||||||
dropbox = "^12.0.2"
|
|
||||||
pytz = "^2024.1"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = [
|
||||||
|
".bzr",
|
||||||
|
".direnv",
|
||||||
|
".eggs",
|
||||||
|
".git",
|
||||||
|
".git-rewrite",
|
||||||
|
".hg",
|
||||||
|
".ipynb_checkpoints",
|
||||||
|
".mypy_cache",
|
||||||
|
".nox",
|
||||||
|
".pants.d",
|
||||||
|
".pyenv",
|
||||||
|
".pytest_cache",
|
||||||
|
".pytype",
|
||||||
|
".ruff_cache",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
".vscode",
|
||||||
|
"__pypackages__",
|
||||||
|
"_build",
|
||||||
|
"buck-out",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"site-packages",
|
||||||
|
"venv",
|
||||||
|
"examples",
|
||||||
|
"tmp",
|
||||||
|
"tests",
|
||||||
|
".archive",
|
||||||
|
"stubs",
|
||||||
|
]
|
||||||
|
|
||||||
|
indent-width = 4
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py312"
|
||||||
|
|
||||||
|
# Ruff Linting Configuration
|
||||||
|
[tool.ruff.lint]
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
fixable = ["ALL"]
|
||||||
|
ignore = ["E501", "N814", "PLR0913", "PLR2004"]
|
||||||
|
select = [
|
||||||
|
"I", # isort
|
||||||
|
"E", # pycodestyle-error
|
||||||
|
"F", # pyflakes
|
||||||
|
"PERF", # perflint
|
||||||
|
"N", # pep8-naming
|
||||||
|
"TRY", # tryceratops
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"FURB", # refurb
|
||||||
|
"PL", # pylint
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"SIM", # flake8-simplify
|
||||||
|
"ASYNC", # flake8-async
|
||||||
|
"A", # flake8-builtins
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"DTZ", # flake8-datetimez
|
||||||
|
"EM", # flake8-errmsg
|
||||||
|
"PIE", # flake8-pie
|
||||||
|
"T20", # flake8-print
|
||||||
|
"Q", # flake8-quotes
|
||||||
|
"RET", # flake8-return
|
||||||
|
"PTH", # flake8-use-pathlib
|
||||||
|
"INP", # flake8-no-pep420
|
||||||
|
"RSE", # flake8-raise
|
||||||
|
"ICN", # flake8-import-conventions
|
||||||
|
"RUF", # ruff
|
||||||
|
]
|
||||||
|
unfixable = []
|
||||||
|
|
||||||
|
# Ruff Formatting Configuration
|
||||||
|
[tool.ruff.format]
|
||||||
|
docstring-code-format = true
|
||||||
|
docstring-code-line-length = "dynamic"
|
||||||
|
indent-style = "space"
|
||||||
|
line-ending = "lf"
|
||||||
|
quote-style = "double"
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
# Pyright Configuration
|
||||||
|
[tool.pyright]
|
||||||
|
defineConstant = {DEBUG = true}
|
||||||
|
exclude = [
|
||||||
|
".direnv",
|
||||||
|
".eggs",
|
||||||
|
".git",
|
||||||
|
".hg",
|
||||||
|
".ipynb_checkpoints",
|
||||||
|
".mypy_cache",
|
||||||
|
".nox",
|
||||||
|
".pants.d",
|
||||||
|
".pyenv",
|
||||||
|
".pytest_cache",
|
||||||
|
".pytype",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
".vscode",
|
||||||
|
"__pypackages__",
|
||||||
|
"_build",
|
||||||
|
"buck-out",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"site-packages",
|
||||||
|
"venv",
|
||||||
|
"examples",
|
||||||
|
"tests",
|
||||||
|
".archive",
|
||||||
|
"stubs",
|
||||||
|
]
|
||||||
|
include = ["**/*.py"]
|
||||||
|
pythonPlatform = "Linux"
|
||||||
|
pythonVersion = "3.12"
|
||||||
|
reportMissingTypeStubs = true
|
||||||
|
reportShadowedImports = false
|
||||||
|
stubPath = "./stubs"
|
||||||
|
typeCheckingMode = "strict"
|
||||||
|
venv = ".venv"
|
||||||
|
venvPath = "."
|
||||||
|
|
0
services/__init__.py
Normal file
0
services/__init__.py
Normal file
|
@ -4,20 +4,19 @@ from db import database
|
||||||
|
|
||||||
|
|
||||||
class Currency:
|
class Currency:
|
||||||
def __init__(self, user_id):
|
def __init__(self, user_id: int) -> None:
|
||||||
self.user_id = user_id
|
self.user_id: int = user_id
|
||||||
self.balance = Currency.fetch_or_create_balance(self.user_id)
|
self.balance: int = Currency.fetch_or_create_balance(self.user_id)
|
||||||
|
|
||||||
def add_balance(self, amount):
|
def add_balance(self, amount: int) -> None:
|
||||||
self.balance += abs(amount)
|
self.balance += abs(amount)
|
||||||
|
|
||||||
def take_balance(self, amount):
|
def take_balance(self, amount: int) -> None:
|
||||||
self.balance -= abs(amount)
|
self.balance -= abs(amount)
|
||||||
|
|
||||||
self.balance = max(self.balance, 0)
|
self.balance = max(self.balance, 0)
|
||||||
|
|
||||||
def push(self):
|
def push(self) -> None:
|
||||||
query = """
|
query: str = """
|
||||||
UPDATE currency
|
UPDATE currency
|
||||||
SET balance = %s
|
SET balance = %s
|
||||||
WHERE user_id = %s
|
WHERE user_id = %s
|
||||||
|
@ -26,15 +25,15 @@ class Currency:
|
||||||
database.execute_query(query, (round(self.balance), self.user_id))
|
database.execute_query(query, (round(self.balance), self.user_id))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def fetch_or_create_balance(user_id):
|
def fetch_or_create_balance(user_id: int) -> int:
|
||||||
query = """
|
query: str = """
|
||||||
SELECT balance
|
SELECT balance
|
||||||
FROM currency
|
FROM currency
|
||||||
WHERE user_id = %s
|
WHERE user_id = %s
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
balance = database.select_query_one(query, (user_id,))
|
balance: int | None = database.select_query_one(query, (user_id,))
|
||||||
except (IndexError, TypeError):
|
except (IndexError, TypeError):
|
||||||
balance = None
|
balance = None
|
||||||
|
|
||||||
|
@ -52,31 +51,27 @@ class Currency:
|
||||||
return balance
|
return balance
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_leaderboard():
|
def load_leaderboard() -> list[tuple[int, int, int]]:
|
||||||
query = "SELECT user_id, balance FROM currency ORDER BY balance DESC"
|
query: str = "SELECT user_id, balance FROM currency ORDER BY balance DESC"
|
||||||
data = database.select_query(query)
|
data: list[tuple[int, int]] = database.select_query(query)
|
||||||
|
|
||||||
return [(row[0], row[1], rank) for rank, row in enumerate(data, start=1)]
|
return [(row[0], row[1], rank) for rank, row in enumerate(data, start=1)]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format(num):
|
def format(num: int) -> str:
|
||||||
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
|
locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
|
||||||
return locale.format_string("%d", num, grouping=True)
|
return locale.format_string("%d", num, grouping=True)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def format_human(num):
|
def format_human(num: int) -> str:
|
||||||
num = float("{:.3g}".format(num))
|
num_float: float = float(f"{num:.3g}")
|
||||||
magnitude = 0
|
magnitude: int = 0
|
||||||
while abs(num) >= 1000:
|
while abs(num_float) >= 1000:
|
||||||
magnitude += 1
|
magnitude += 1
|
||||||
num /= 1000.0
|
num_float /= 1000.0
|
||||||
|
|
||||||
return "{}{}".format(
|
suffixes: list[str] = ["", "K", "M", "B", "T", "Q", "Qi", "Sx", "Sp", "Oc", "No", "Dc"]
|
||||||
"{:f}".format(num).rstrip("0").rstrip("."),
|
return f'{f"{num_float:f}".rstrip("0").rstrip(".")}{suffixes[magnitude]}'
|
||||||
["", "K", "M", "B", "T", "Q", "Qi", "Sx", "Sp", "Oc", "No", "Dc"][
|
|
||||||
magnitude
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# A Thousand = K
|
# A Thousand = K
|
||||||
# Million = M
|
# Million = M
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ class Dailies:
|
||||||
microsecond=0,
|
microsecond=0,
|
||||||
)
|
)
|
||||||
|
|
||||||
data: Tuple[Optional[str], int] = Dailies.get_data(user_id)
|
data: tuple[str | None, int] = Dailies.get_data(user_id)
|
||||||
|
|
||||||
if data[0] is not None:
|
if data[0] is not None:
|
||||||
self.claimed_at: datetime = datetime.fromisoformat(data[0])
|
self.claimed_at: datetime = datetime.fromisoformat(data[0])
|
||||||
|
@ -38,7 +37,7 @@ class Dailies:
|
||||||
INSERT INTO dailies (user_id, amount, claimed_at, streak)
|
INSERT INTO dailies (user_id, amount, claimed_at, streak)
|
||||||
VALUES (%s, %s, %s, %s)
|
VALUES (%s, %s, %s, %s)
|
||||||
"""
|
"""
|
||||||
values: Tuple[int, int, str, int] = (
|
values: tuple[int, int, str, int] = (
|
||||||
self.user_id,
|
self.user_id,
|
||||||
self.amount,
|
self.amount,
|
||||||
self.claimed_at.isoformat(),
|
self.claimed_at.isoformat(),
|
||||||
|
@ -51,9 +50,6 @@ class Dailies:
|
||||||
cash.push()
|
cash.push()
|
||||||
|
|
||||||
def can_be_claimed(self) -> bool:
|
def can_be_claimed(self) -> bool:
|
||||||
if self.claimed_at is None:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if self.time_now < self.reset_time:
|
if self.time_now < self.reset_time:
|
||||||
self.reset_time -= timedelta(days=1)
|
self.reset_time -= timedelta(days=1)
|
||||||
|
|
||||||
|
@ -68,21 +64,14 @@ class Dailies:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
check_1: bool = (
|
check_1: bool = self.claimed_at.date() == (self.time_now - timedelta(days=1)).date()
|
||||||
self.claimed_at.date() == (self.time_now - timedelta(days=1)).date()
|
check_2: bool = self.claimed_at.date() == (self.time_now - timedelta(days=2)).date()
|
||||||
)
|
check_3: bool = self.claimed_at.date() == self.time_now.date() and self.claimed_at < self.reset_time
|
||||||
check_2: bool = (
|
|
||||||
self.claimed_at.date() == (self.time_now - timedelta(days=2)).date()
|
|
||||||
)
|
|
||||||
check_3: bool = (
|
|
||||||
self.claimed_at.date() == self.time_now.date()
|
|
||||||
and self.claimed_at < self.reset_time
|
|
||||||
)
|
|
||||||
|
|
||||||
return check_1 or check_2 or check_3
|
return check_1 or check_2 or check_3
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_data(user_id: int) -> Tuple[Optional[str], int]:
|
def get_data(user_id: int) -> tuple[str | None, int]:
|
||||||
query: str = """
|
query: str = """
|
||||||
SELECT claimed_at, streak
|
SELECT claimed_at, streak
|
||||||
FROM dailies
|
FROM dailies
|
||||||
|
@ -101,7 +90,7 @@ class Dailies:
|
||||||
return claimed_at, streak
|
return claimed_at, streak
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_leaderboard() -> List[Tuple[int, int, str, int]]:
|
def load_leaderboard() -> list[tuple[int, int, str, int]]:
|
||||||
query: str = """
|
query: str = """
|
||||||
SELECT user_id, MAX(streak), claimed_at
|
SELECT user_id, MAX(streak), claimed_at
|
||||||
FROM dailies
|
FROM dailies
|
||||||
|
@ -109,9 +98,9 @@ class Dailies:
|
||||||
ORDER BY MAX(streak) DESC;
|
ORDER BY MAX(streak) DESC;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data: List[Tuple[int, int, str]] = database.select_query(query)
|
data: list[tuple[int, int, str]] = database.select_query(query)
|
||||||
|
|
||||||
leaderboard: List[Tuple[int, int, str, int]] = [
|
leaderboard: list[tuple[int, int, str, int]] = [
|
||||||
(row[0], row[1], row[2], rank) for rank, row in enumerate(data, start=1)
|
(row[0], row[1], row[2], rank) for rank, row in enumerate(data, start=1)
|
||||||
]
|
]
|
||||||
return leaderboard
|
return leaderboard
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import time
|
import time
|
||||||
from typing import Callable, Dict, List, Optional, Tuple
|
from collections.abc import Callable
|
||||||
|
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class XpService:
|
||||||
self.guild_id: int = guild_id
|
self.guild_id: int = guild_id
|
||||||
self.xp: int = 0
|
self.xp: int = 0
|
||||||
self.level: int = 0
|
self.level: int = 0
|
||||||
self.cooldown_time: Optional[float] = None
|
self.cooldown_time: float | None = None
|
||||||
self.xp_gain: int = CONST.XP_GAIN_PER_MESSAGE
|
self.xp_gain: int = CONST.XP_GAIN_PER_MESSAGE
|
||||||
self.new_cooldown: int = CONST.XP_GAIN_COOLDOWN
|
self.new_cooldown: int = CONST.XP_GAIN_COOLDOWN
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ class XpService:
|
||||||
self.level = user_level
|
self.level = user_level
|
||||||
self.cooldown_time = cooldown
|
self.cooldown_time = cooldown
|
||||||
|
|
||||||
def calculate_rank(self) -> Optional[int]:
|
def calculate_rank(self) -> int | None:
|
||||||
"""
|
"""
|
||||||
Determines the rank of a user in the guild based on their XP and level.
|
Determines the rank of a user in the guild based on their XP and level.
|
||||||
|
|
||||||
|
@ -83,12 +83,12 @@ class XpService:
|
||||||
WHERE guild_id = %s
|
WHERE guild_id = %s
|
||||||
ORDER BY user_level DESC, user_xp DESC
|
ORDER BY user_level DESC, user_xp DESC
|
||||||
"""
|
"""
|
||||||
data: List[Tuple[int, int, int]] = database.select_query(
|
data: list[tuple[int, int, int]] = database.select_query(
|
||||||
query,
|
query,
|
||||||
(self.guild_id,),
|
(self.guild_id,),
|
||||||
)
|
)
|
||||||
|
|
||||||
leaderboard: List[Tuple[int, int, int, int]] = [
|
leaderboard: list[tuple[int, int, int, int]] = [
|
||||||
(row[0], row[1], row[2], rank) for rank, row in enumerate(data, start=1)
|
(row[0], row[1], row[2], rank) for rank, row in enumerate(data, start=1)
|
||||||
]
|
]
|
||||||
return next(
|
return next(
|
||||||
|
@ -97,7 +97,7 @@ class XpService:
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_leaderboard(guild_id: int) -> List[Tuple[int, int, int, int]]:
|
def load_leaderboard(guild_id: int) -> list[tuple[int, int, int, int]]:
|
||||||
"""
|
"""
|
||||||
Retrieves the guild's XP leaderboard.
|
Retrieves the guild's XP leaderboard.
|
||||||
|
|
||||||
|
@ -113,9 +113,9 @@ class XpService:
|
||||||
WHERE guild_id = %s
|
WHERE guild_id = %s
|
||||||
ORDER BY user_level DESC, user_xp DESC
|
ORDER BY user_level DESC, user_xp DESC
|
||||||
"""
|
"""
|
||||||
data: List[Tuple[int, int, int]] = database.select_query(query, (guild_id,))
|
data: list[tuple[int, int, int]] = database.select_query(query, (guild_id,))
|
||||||
|
|
||||||
leaderboard: List[Tuple[int, int, int, int]] = []
|
leaderboard: list[tuple[int, int, int, int]] = []
|
||||||
for row in data:
|
for row in data:
|
||||||
row_user_id: int = row[0]
|
row_user_id: int = row[0]
|
||||||
user_xp: int = row[1]
|
user_xp: int = row[1]
|
||||||
|
@ -164,7 +164,7 @@ class XpService:
|
||||||
Returns:
|
Returns:
|
||||||
int: The amount of XP needed for the next level.
|
int: The amount of XP needed for the next level.
|
||||||
"""
|
"""
|
||||||
formula_mapping: Dict[Tuple[int, int], Callable[[int], int]] = {
|
formula_mapping: dict[tuple[int, int], Callable[[int], int]] = {
|
||||||
(10, 19): lambda level: 12 * level + 28,
|
(10, 19): lambda level: 12 * level + 28,
|
||||||
(20, 29): lambda level: 15 * level + 29,
|
(20, 29): lambda level: 15 * level + 29,
|
||||||
(30, 39): lambda level: 18 * level + 30,
|
(30, 39): lambda level: 18 * level + 30,
|
||||||
|
@ -182,11 +182,7 @@ class XpService:
|
||||||
for level_range, formula in formula_mapping.items()
|
for level_range, formula in formula_mapping.items()
|
||||||
if level_range[0] <= current_level <= level_range[1]
|
if level_range[0] <= current_level <= level_range[1]
|
||||||
),
|
),
|
||||||
(
|
(10 * current_level + 27 if current_level < 10 else 42 * current_level + 37),
|
||||||
10 * current_level + 27
|
|
||||||
if current_level < 10
|
|
||||||
else 42 * current_level + 37
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -203,9 +199,9 @@ class XpRewardService:
|
||||||
guild_id (int): The ID of the guild.
|
guild_id (int): The ID of the guild.
|
||||||
"""
|
"""
|
||||||
self.guild_id: int = guild_id
|
self.guild_id: int = guild_id
|
||||||
self.rewards: Dict[int, Tuple[int, bool]] = self._fetch_rewards()
|
self.rewards: dict[int, tuple[int, bool]] = self._fetch_rewards()
|
||||||
|
|
||||||
def _fetch_rewards(self) -> Dict[int, Tuple[int, bool]]:
|
def _fetch_rewards(self) -> dict[int, tuple[int, bool]]:
|
||||||
"""
|
"""
|
||||||
Retrieves the XP rewards for the guild from the database.
|
Retrieves the XP rewards for the guild from the database.
|
||||||
|
|
||||||
|
@ -218,7 +214,7 @@ class XpRewardService:
|
||||||
WHERE guild_id = %s
|
WHERE guild_id = %s
|
||||||
ORDER BY level DESC
|
ORDER BY level DESC
|
||||||
"""
|
"""
|
||||||
data: List[Tuple[int, int, bool]] = database.select_query(
|
data: list[tuple[int, int, bool]] = database.select_query(
|
||||||
query,
|
query,
|
||||||
(self.guild_id,),
|
(self.guild_id,),
|
||||||
)
|
)
|
||||||
|
@ -237,7 +233,8 @@ class XpRewardService:
|
||||||
commands.BadArgument: If the server has more than 25 XP rewards.
|
commands.BadArgument: If the server has more than 25 XP rewards.
|
||||||
"""
|
"""
|
||||||
if len(self.rewards) >= 25:
|
if len(self.rewards) >= 25:
|
||||||
raise commands.BadArgument("A server can't have more than 25 XP rewards.")
|
msg = "A server can't have more than 25 XP rewards."
|
||||||
|
raise commands.BadArgument(msg)
|
||||||
|
|
||||||
query: str = """
|
query: str = """
|
||||||
INSERT INTO level_rewards (guild_id, level, role_id, persistent)
|
INSERT INTO level_rewards (guild_id, level, role_id, persistent)
|
||||||
|
@ -264,7 +261,7 @@ class XpRewardService:
|
||||||
database.execute_query(query, (self.guild_id, level))
|
database.execute_query(query, (self.guild_id, level))
|
||||||
self.rewards.pop(level, None)
|
self.rewards.pop(level, None)
|
||||||
|
|
||||||
def get_role(self, level: int) -> Optional[int]:
|
def get_role(self, level: int) -> int | None:
|
||||||
"""
|
"""
|
||||||
Retrieves the role ID for a given level.
|
Retrieves the role ID for a given level.
|
||||||
|
|
||||||
|
@ -276,7 +273,7 @@ class XpRewardService:
|
||||||
"""
|
"""
|
||||||
return self.rewards.get(level, (None,))[0]
|
return self.rewards.get(level, (None,))[0]
|
||||||
|
|
||||||
def should_replace_previous_reward(self, level: int) -> Tuple[Optional[int], bool]:
|
def should_replace_previous_reward(self, level: int) -> tuple[int | None, bool]:
|
||||||
"""
|
"""
|
||||||
Checks if the previous reward should be replaced based on the given level.
|
Checks if the previous reward should be replaced based on the given level.
|
||||||
|
|
||||||
|
|
|
@ -92,8 +92,8 @@ info:
|
||||||
invite_url: https://discord.com/oauth2/authorize?client_id=1038050427272429588&permissions=8&scope=bot
|
invite_url: https://discord.com/oauth2/authorize?client_id=1038050427272429588&permissions=8&scope=bot
|
||||||
|
|
||||||
introductions:
|
introductions:
|
||||||
intro_guild_id: 719227135151046700
|
intro_guild_id: 1219635811977269371
|
||||||
intro_channel_id: 973619250507972600
|
intro_channel_id: 1219637688328523806
|
||||||
intro_question_mapping:
|
intro_question_mapping:
|
||||||
Nickname: how would you like to be identified in the server? (nickname)
|
Nickname: how would you like to be identified in the server? (nickname)
|
||||||
Age: how old are you?
|
Age: how old are you?
|
||||||
|
|
0
ui/__init__.py
Normal file
0
ui/__init__.py
Normal file
34
ui/embeds.py
34
ui/embeds.py
|
@ -1,28 +1,28 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Literal
|
from typing import Literal
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
|
|
||||||
|
|
||||||
class builder:
|
class Builder:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_embed(
|
def create_embed(
|
||||||
user_name: Optional[str] = None,
|
user_name: str | None = None,
|
||||||
user_display_avatar_url: Optional[str] = None,
|
user_display_avatar_url: str | None = None,
|
||||||
theme: Optional[Literal["error", "success", "info", "warning"]] = None,
|
theme: Literal["error", "success", "info", "warning"] | None = None,
|
||||||
title: Optional[str] = None,
|
title: str | None = None,
|
||||||
author_text: Optional[str] = None,
|
author_text: str | None = None,
|
||||||
author_icon_url: Optional[str] = None,
|
author_icon_url: str | None = None,
|
||||||
author_url: Optional[str] = None,
|
author_url: str | None = None,
|
||||||
description: Optional[str] = None,
|
description: str | None = None,
|
||||||
color: Optional[int] = None,
|
color: int | None = None,
|
||||||
footer_text: Optional[str] = None,
|
footer_text: str | None = None,
|
||||||
footer_icon_url: Optional[str] = None,
|
footer_icon_url: str | None = None,
|
||||||
image_url: Optional[str] = None,
|
image_url: str | None = None,
|
||||||
thumbnail_url: Optional[str] = None,
|
thumbnail_url: str | None = None,
|
||||||
timestamp: Optional[datetime] = None,
|
timestamp: datetime | None = None,
|
||||||
hide_name_in_description: bool = False,
|
hide_name_in_description: bool = False,
|
||||||
hide_time: bool = False,
|
hide_time: bool = False,
|
||||||
) -> discord.Embed:
|
) -> discord.Embed:
|
||||||
|
@ -59,7 +59,7 @@ class builder:
|
||||||
icon_url=footer_icon_url or CONST.LUMI_LOGO_TRANSPARENT,
|
icon_url=footer_icon_url or CONST.LUMI_LOGO_TRANSPARENT,
|
||||||
)
|
)
|
||||||
|
|
||||||
embed.timestamp = None if hide_time else (timestamp or datetime.now())
|
embed.timestamp = None if hide_time else (timestamp or discord.utils.utcnow())
|
||||||
if image_url:
|
if image_url:
|
||||||
embed.set_image(url=image_url)
|
embed.set_image(url=image_url)
|
||||||
if thumbnail_url:
|
if thumbnail_url:
|
||||||
|
|
0
ui/views/__init__.py
Normal file
0
ui/views/__init__.py
Normal file
|
@ -1,8 +1,8 @@
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.ui import View, Button
|
from discord.ui import Button, View
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
class BlackJackButtons(View):
|
class BlackJackButtons(View):
|
||||||
|
@ -12,14 +12,13 @@ class BlackJackButtons(View):
|
||||||
self.clickedHit: bool = False
|
self.clickedHit: bool = False
|
||||||
self.clickedStand: bool = False
|
self.clickedStand: bool = False
|
||||||
self.clickedDoubleDown: bool = False
|
self.clickedDoubleDown: bool = False
|
||||||
self.message: Optional[discord.Message] = None
|
self.message: discord.Message | None = None
|
||||||
|
|
||||||
async def on_timeout(self) -> None:
|
async def on_timeout(self) -> None:
|
||||||
self.children: List[discord.ui.Button] = []
|
self.children: list[discord.ui.Button[View]] = []
|
||||||
|
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
if isinstance(child, Button):
|
child.disabled = True
|
||||||
child.disabled = True
|
|
||||||
|
|
||||||
@discord.ui.button(
|
@discord.ui.button(
|
||||||
label=CONST.STRINGS["blackjack_hit"],
|
label=CONST.STRINGS["blackjack_hit"],
|
||||||
|
@ -29,7 +28,7 @@ class BlackJackButtons(View):
|
||||||
async def hit_button_callback(
|
async def hit_button_callback(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
button: Button,
|
button: Button[View],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.clickedHit = True
|
self.clickedHit = True
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
@ -43,7 +42,7 @@ class BlackJackButtons(View):
|
||||||
async def stand_button_callback(
|
async def stand_button_callback(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
button: Button,
|
button: Button[View],
|
||||||
) -> None:
|
) -> None:
|
||||||
self.clickedStand = True
|
self.clickedStand = True
|
||||||
await interaction.response.defer()
|
await interaction.response.defer()
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.ui import Button, View
|
from discord.ui import Button, View
|
||||||
|
@ -11,7 +9,7 @@ class IntroductionStartButtons(View):
|
||||||
self.ctx: commands.Context[commands.Bot] = ctx
|
self.ctx: commands.Context[commands.Bot] = ctx
|
||||||
self.clicked_start: bool = False
|
self.clicked_start: bool = False
|
||||||
self.clicked_stop: bool = False
|
self.clicked_stop: bool = False
|
||||||
self.message: Optional[discord.Message] = None
|
self.message: discord.Message | None = None
|
||||||
|
|
||||||
async def on_timeout(self) -> None:
|
async def on_timeout(self) -> None:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
|
@ -24,7 +22,7 @@ class IntroductionStartButtons(View):
|
||||||
async def start_button_callback(
|
async def start_button_callback(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
button: Button,
|
button: Button[View],
|
||||||
) -> None:
|
) -> None:
|
||||||
await interaction.response.edit_message(view=None)
|
await interaction.response.edit_message(view=None)
|
||||||
self.clicked_start = True
|
self.clicked_start = True
|
||||||
|
@ -34,7 +32,7 @@ class IntroductionStartButtons(View):
|
||||||
async def stop_button_callback(
|
async def stop_button_callback(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
button: Button,
|
button: Button[View],
|
||||||
) -> None:
|
) -> None:
|
||||||
await interaction.response.edit_message(view=None)
|
await interaction.response.edit_message(view=None)
|
||||||
self.clicked_stop = True
|
self.clicked_stop = True
|
||||||
|
@ -46,7 +44,7 @@ class IntroductionFinishButtons(View):
|
||||||
super().__init__(timeout=60)
|
super().__init__(timeout=60)
|
||||||
self.ctx: commands.Context[commands.Bot] = ctx
|
self.ctx: commands.Context[commands.Bot] = ctx
|
||||||
self.clicked_confirm: bool = False
|
self.clicked_confirm: bool = False
|
||||||
self.message: Optional[discord.Message] = None
|
self.message: discord.Message | None = None
|
||||||
|
|
||||||
async def on_timeout(self) -> None:
|
async def on_timeout(self) -> None:
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
|
@ -59,7 +57,7 @@ class IntroductionFinishButtons(View):
|
||||||
async def confirm_button_callback(
|
async def confirm_button_callback(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
button: Button,
|
button: Button[View],
|
||||||
) -> None:
|
) -> None:
|
||||||
await interaction.response.edit_message(view=None)
|
await interaction.response.edit_message(view=None)
|
||||||
self.clicked_confirm = True
|
self.clicked_confirm = True
|
||||||
|
@ -69,7 +67,7 @@ class IntroductionFinishButtons(View):
|
||||||
async def stop_button_callback(
|
async def stop_button_callback(
|
||||||
self,
|
self,
|
||||||
interaction: discord.Interaction,
|
interaction: discord.Interaction,
|
||||||
button: Button,
|
button: Button[View],
|
||||||
) -> None:
|
) -> None:
|
||||||
await interaction.response.edit_message(view=None)
|
await interaction.response.edit_message(view=None)
|
||||||
self.stop()
|
self.stop()
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from discord import ButtonStyle
|
from discord import ButtonStyle
|
||||||
from discord.ui import Button, View
|
from discord.ui import Button, View
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
|
|
||||||
|
|
||||||
class InviteButton(View):
|
class InviteButton(View):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(timeout=None)
|
super().__init__(timeout=None)
|
||||||
invite_button: Button = Button(
|
invite_button: Button[InviteButton] = Button(
|
||||||
label=CONST.STRINGS["invite_button_text"],
|
label=CONST.STRINGS["invite_button_text"],
|
||||||
style=ButtonStyle.url,
|
style=ButtonStyle.url,
|
||||||
url=CONST.INVITE_URL,
|
url=CONST.INVITE_URL,
|
||||||
|
|
|
@ -1,16 +1,17 @@
|
||||||
|
import contextlib
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
|
|
||||||
from lib.const import CONST
|
from lib.const import CONST
|
||||||
from ui.embeds import builder
|
|
||||||
from services.currency_service import Currency
|
from services.currency_service import Currency
|
||||||
from services.daily_service import Dailies
|
from services.daily_service import Dailies
|
||||||
from services.xp_service import XpService
|
from services.xp_service import XpService
|
||||||
|
from ui.embeds import Builder
|
||||||
|
|
||||||
|
|
||||||
class LeaderboardCommandOptions(discord.ui.Select):
|
class LeaderboardCommandOptions(discord.ui.Select[discord.ui.View]):
|
||||||
"""
|
"""
|
||||||
This class specifies the options for the leaderboard command:
|
This class specifies the options for the leaderboard command:
|
||||||
- XP
|
- XP
|
||||||
|
@ -19,34 +20,35 @@ class LeaderboardCommandOptions(discord.ui.Select):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
options: list[discord.SelectOption] = [
|
||||||
|
discord.SelectOption(
|
||||||
|
label="Levels",
|
||||||
|
description="See the top chatters of this server!",
|
||||||
|
emoji="🆙",
|
||||||
|
value="xp",
|
||||||
|
),
|
||||||
|
discord.SelectOption(
|
||||||
|
label="Currency",
|
||||||
|
description="Who is the richest Lumi user?",
|
||||||
|
value="currency",
|
||||||
|
emoji="💸",
|
||||||
|
),
|
||||||
|
discord.SelectOption(
|
||||||
|
label="Dailies",
|
||||||
|
description="See who has the biggest streak!",
|
||||||
|
value="dailies",
|
||||||
|
emoji="📅",
|
||||||
|
),
|
||||||
|
]
|
||||||
super().__init__(
|
super().__init__(
|
||||||
placeholder="Select a leaderboard",
|
placeholder="Select a leaderboard",
|
||||||
min_values=1,
|
min_values=1,
|
||||||
max_values=1,
|
max_values=1,
|
||||||
options=[
|
options=options,
|
||||||
discord.SelectOption(
|
|
||||||
label="Levels",
|
|
||||||
description="See the top chatters of this server!",
|
|
||||||
emoji="🆙",
|
|
||||||
value="xp",
|
|
||||||
),
|
|
||||||
discord.SelectOption(
|
|
||||||
label="Currency",
|
|
||||||
description="Who is the richest Lumi user?",
|
|
||||||
value="currency",
|
|
||||||
emoji="💸",
|
|
||||||
),
|
|
||||||
discord.SelectOption(
|
|
||||||
label="Dailies",
|
|
||||||
description="See who has the biggest streak!",
|
|
||||||
value="dailies",
|
|
||||||
emoji="📅",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def callback(self, interaction: discord.Interaction) -> None:
|
async def callback(self, interaction: discord.Interaction) -> None:
|
||||||
if self.view:
|
if isinstance(self.view, LeaderboardCommandView):
|
||||||
await self.view.on_select(self.values[0], interaction)
|
await self.view.on_select(self.values[0], interaction)
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,15 +58,17 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
what kind of leaderboard to show.
|
what kind of leaderboard to show.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
ctx: commands.Context[commands.Bot]
|
||||||
|
options: LeaderboardCommandOptions
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ctx: commands.Context[commands.Bot],
|
ctx: commands.Context[commands.Bot],
|
||||||
options: LeaderboardCommandOptions,
|
options: LeaderboardCommandOptions,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
super().__init__(timeout=180)
|
||||||
self.ctx = ctx
|
self.ctx = ctx
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
super().__init__(timeout=180)
|
|
||||||
self.add_item(self.options)
|
self.add_item(self.options)
|
||||||
|
|
||||||
async def on_timeout(self) -> None:
|
async def on_timeout(self) -> None:
|
||||||
|
@ -72,7 +76,7 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
|
|
||||||
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
async def interaction_check(self, interaction: discord.Interaction) -> bool:
|
||||||
if interaction.user and interaction.user != self.ctx.author:
|
if interaction.user and interaction.user != self.ctx.author:
|
||||||
embed = builder.create_embed(
|
embed = Builder.create_embed(
|
||||||
theme="error",
|
theme="error",
|
||||||
user_name=interaction.user.name,
|
user_name=interaction.user.name,
|
||||||
description=CONST.STRINGS["xp_lb_cant_use_dropdown"],
|
description=CONST.STRINGS["xp_lb_cant_use_dropdown"],
|
||||||
|
@ -86,7 +90,7 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
if not self.ctx.guild:
|
if not self.ctx.guild:
|
||||||
return
|
return
|
||||||
|
|
||||||
embed = builder.create_embed(
|
embed = Builder.create_embed(
|
||||||
theme="success",
|
theme="success",
|
||||||
user_name=interaction.user.name,
|
user_name=interaction.user.name,
|
||||||
thumbnail_url=CONST.FLOWERS_ART,
|
thumbnail_url=CONST.FLOWERS_ART,
|
||||||
|
@ -99,7 +103,12 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
|
|
||||||
await interaction.response.edit_message(embed=embed)
|
await interaction.response.edit_message(embed=embed)
|
||||||
|
|
||||||
async def populate_leaderboard(self, item: str, embed, icon):
|
async def populate_leaderboard(
|
||||||
|
self,
|
||||||
|
item: str,
|
||||||
|
embed: discord.Embed,
|
||||||
|
icon: str,
|
||||||
|
) -> None:
|
||||||
leaderboard_methods = {
|
leaderboard_methods = {
|
||||||
"xp": self._populate_xp_leaderboard,
|
"xp": self._populate_xp_leaderboard,
|
||||||
"currency": self._populate_currency_leaderboard,
|
"currency": self._populate_currency_leaderboard,
|
||||||
|
@ -107,11 +116,13 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
}
|
}
|
||||||
await leaderboard_methods[item](embed, icon)
|
await leaderboard_methods[item](embed, icon)
|
||||||
|
|
||||||
async def _populate_xp_leaderboard(self, embed, icon):
|
async def _populate_xp_leaderboard(self, embed: discord.Embed, icon: str) -> None:
|
||||||
if not self.ctx.guild:
|
if not self.ctx.guild:
|
||||||
return
|
return
|
||||||
|
|
||||||
xp_lb = XpService.load_leaderboard(self.ctx.guild.id)
|
xp_lb: list[tuple[int, int, int, int]] = XpService.load_leaderboard(
|
||||||
|
self.ctx.guild.id,
|
||||||
|
)
|
||||||
embed.set_author(name=CONST.STRINGS["xp_lb_author"], icon_url=icon)
|
embed.set_author(name=CONST.STRINGS["xp_lb_author"], icon_url=icon)
|
||||||
|
|
||||||
for rank, (user_id, xp, level, xp_needed_for_next_level) in enumerate(
|
for rank, (user_id, xp, level, xp_needed_for_next_level) in enumerate(
|
||||||
|
@ -133,20 +144,22 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _populate_currency_leaderboard(self, embed, icon):
|
async def _populate_currency_leaderboard(
|
||||||
|
self,
|
||||||
|
embed: discord.Embed,
|
||||||
|
icon: str,
|
||||||
|
) -> None:
|
||||||
if not self.ctx.guild:
|
if not self.ctx.guild:
|
||||||
return
|
return
|
||||||
|
|
||||||
cash_lb = Currency.load_leaderboard()
|
cash_lb: list[tuple[int, int, int]] = Currency.load_leaderboard()
|
||||||
embed.set_author(name=CONST.STRINGS["xp_lb_currency_author"], icon_url=icon)
|
embed.set_author(name=CONST.STRINGS["xp_lb_currency_author"], icon_url=icon)
|
||||||
embed.set_thumbnail(url=CONST.TEAPOT_ART)
|
embed.set_thumbnail(url=CONST.TEAPOT_ART)
|
||||||
|
|
||||||
for user_id, balance, rank in cash_lb[:5]:
|
for user_id, balance, rank in cash_lb[:5]:
|
||||||
try:
|
member: discord.Member | None = None
|
||||||
|
with contextlib.suppress(discord.HTTPException):
|
||||||
member = await self.ctx.guild.fetch_member(user_id)
|
member = await self.ctx.guild.fetch_member(user_id)
|
||||||
except discord.HTTPException:
|
|
||||||
member = None
|
|
||||||
|
|
||||||
name = member.name if member else str(user_id)
|
name = member.name if member else str(user_id)
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
|
@ -157,29 +170,31 @@ class LeaderboardCommandView(discord.ui.View):
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _populate_dailies_leaderboard(self, embed, icon):
|
async def _populate_dailies_leaderboard(
|
||||||
|
self,
|
||||||
|
embed: discord.Embed,
|
||||||
|
icon: str,
|
||||||
|
) -> None:
|
||||||
if not self.ctx.guild:
|
if not self.ctx.guild:
|
||||||
return
|
return
|
||||||
|
|
||||||
daily_lb = Dailies.load_leaderboard()
|
daily_lb: list[tuple[int, int, str, int]] = Dailies.load_leaderboard()
|
||||||
embed.set_author(name=CONST.STRINGS["xp_lb_dailies_author"], icon_url=icon)
|
embed.set_author(name=CONST.STRINGS["xp_lb_dailies_author"], icon_url=icon)
|
||||||
embed.set_thumbnail(url=CONST.MUFFIN_ART)
|
embed.set_thumbnail(url=CONST.MUFFIN_ART)
|
||||||
|
|
||||||
for user_id, streak, claimed_at, rank in daily_lb[:5]:
|
for user_id, streak, claimed_at, rank in daily_lb[:5]:
|
||||||
try:
|
member: discord.Member | None = None
|
||||||
|
with contextlib.suppress(discord.HTTPException):
|
||||||
member = await self.ctx.guild.fetch_member(user_id)
|
member = await self.ctx.guild.fetch_member(user_id)
|
||||||
except discord.HTTPException:
|
name = member.name if member else str(user_id)
|
||||||
member = None
|
|
||||||
|
|
||||||
name = member.name if member else user_id
|
claimed_at_date = datetime.fromisoformat(claimed_at).date()
|
||||||
|
|
||||||
claimed_at = datetime.fromisoformat(claimed_at).date()
|
|
||||||
|
|
||||||
embed.add_field(
|
embed.add_field(
|
||||||
name=f"#{rank} - {name}",
|
name=f"#{rank} - {name}",
|
||||||
value=CONST.STRINGS["xp_lb_dailies_field_value"].format(
|
value=CONST.STRINGS["xp_lb_dailies_field_value"].format(
|
||||||
streak,
|
streak,
|
||||||
claimed_at,
|
claimed_at_date,
|
||||||
),
|
),
|
||||||
inline=False,
|
inline=False,
|
||||||
)
|
)
|
||||||
|
|
0
wrappers/__init__.py
Normal file
0
wrappers/__init__.py
Normal file
|
@ -258,9 +258,7 @@ class Client:
|
||||||
HttpError
|
HttpError
|
||||||
If the request fails.
|
If the request fails.
|
||||||
"""
|
"""
|
||||||
comic_url = (
|
comic_url = self.latest_comic_url() if comic_id <= 0 else self.comic_id_url(comic_id)
|
||||||
self.latest_comic_url() if comic_id <= 0 else self.comic_id_url(comic_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = httpx.get(comic_url)
|
response = httpx.get(comic_url)
|
||||||
|
|
Loading…
Reference in a new issue