Skip to content

Commit

Permalink
[Leveler] Rewrite (#38)
Browse files Browse the repository at this point in the history
Look at GH/#38 for changelog if you brave enough (186 commits - im not going to list all changes)

Co-authored-by: notKqi <[email protected]>
Co-authored-by: Predeactor <[email protected]>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: aikaterna <[email protected]>
Co-authored-by: Moose <[email protected]>
  • Loading branch information
6 people authored Mar 11, 2022
1 parent 28bf72c commit f88312c
Show file tree
Hide file tree
Showing 36 changed files with 4,793 additions and 3,639 deletions.
19 changes: 19 additions & 0 deletions leveler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
from warnings import filterwarnings

filterwarnings("ignore", category=DeprecationWarning, module=r"fontTools.")
from .leveler import Leveler

__red_end_user_data_statement__ = (
"This cog persistently stores next data about user:<br>"
"• Discord user ID<br>"
"• Current username<br>"
"• Per-server XP data (current XP and current level)<br>"
"• Total count of XP on all servers<br>"
"• URLs to profile/rank/levelup backrounds<br>"
"• User-set title & info<br>"
'• User\'s "reputation" points count<br>'
"• Data about user's badges<br>"
"• Data about user's profile/rank/levelup colors<br>"
"• Timestamp of latest message and reputation<br>"
"• Last message MD5 hash (for comparison)<br>"
"This cog supports data removal requests."
)


async def setup(bot):
cog = Leveler(bot)
Expand Down
169 changes: 169 additions & 0 deletions leveler/abc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
from abc import ABC, abstractmethod
from asyncio import Lock
from io import BytesIO
from logging import Logger
from typing import List

from aiohttp import ClientSession
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
from redbot.core import Config, commands
from redbot.core.bot import Red


class MixinMeta(ABC):
"""
Base class for well behaved type hint detection with composite class.
Basically, to keep developers sane when not all attributes are defined in each mixin.
"""

bot: Red
log: Logger

config: Config

_db_ready: bool
_db_lock: Lock
client: AsyncIOMotorClient
db: AsyncIOMotorDatabase
session: ClientSession

@abstractmethod
async def _connect_to_mongo(self):
raise NotImplementedError

@abstractmethod
async def _create_user(self, user, server):
raise NotImplementedError

@abstractmethod
async def _hex_to_rgb(self, hex_num: str, a: int) -> tuple:
raise NotImplementedError

@abstractmethod
async def _badge_convert_dict(self, userinfo):
raise NotImplementedError

@abstractmethod
async def _process_purchase(self, ctx) -> bool:
raise NotImplementedError

@abstractmethod
def _truncate_text(self, text, max_length) -> str:
raise NotImplementedError

@abstractmethod
async def asyncify(self, func, *args, **kwargs):
raise NotImplementedError

@abstractmethod
async def asyncify_thread(self, func, *args, **kwargs):
raise NotImplementedError

@abstractmethod
async def asyncify_process(self, func, *args, **kwargs):
raise NotImplementedError

@abstractmethod
async def hash_with_md5(self, string):
raise NotImplementedError

@abstractmethod
async def _handle_levelup(self, user, userinfo, server, channel):
raise NotImplementedError

@abstractmethod
async def _required_exp(self, level: int) -> int:
raise NotImplementedError

@abstractmethod
async def _level_exp(self, level: int) -> int:
raise NotImplementedError

@abstractmethod
async def _find_level(self, total_exp) -> int:
raise NotImplementedError

@abstractmethod
async def _find_server_rank(self, user, server) -> int:
raise NotImplementedError

@abstractmethod
async def _find_global_rank(self, user) -> int:
raise NotImplementedError

@abstractmethod
async def _find_server_exp(self, user, server) -> int:
raise NotImplementedError

@abstractmethod
async def _find_server_rep_rank(self, user, server) -> int:
raise NotImplementedError

@abstractmethod
async def _find_global_rep_rank(self, user) -> int:
raise NotImplementedError

@abstractmethod
async def draw_profile(self, user, server) -> BytesIO:
raise NotImplementedError

@abstractmethod
async def draw_rank(self, user, server) -> BytesIO:
raise NotImplementedError

@abstractmethod
async def draw_levelup(self, user, server) -> BytesIO:
raise NotImplementedError

@abstractmethod
async def _process_exp(self, message, userinfo, exp: int):
raise NotImplementedError

@abstractmethod
async def _give_chat_credit(self, user, server):
raise NotImplementedError

@abstractmethod
async def _valid_image_url(self, url):
raise NotImplementedError

@abstractmethod
async def _auto_color(self, ctx, url: str, ranks) -> List[str]:
raise NotImplementedError

@abstractmethod
def _center(self, start, end, text, font) -> int:
raise NotImplementedError

@abstractmethod
def char_in_font(self, unicode_char, font) -> bool:
raise NotImplementedError

@abstractmethod
def _contrast(self, bg_color, color1, color2):
raise NotImplementedError

@abstractmethod
def _luminance(self, color):
raise NotImplementedError

@abstractmethod
def _contrast_ratio(self, bgcolor, foreground):
raise NotImplementedError

@abstractmethod
def _name(self, user, max_length) -> str:
raise NotImplementedError

@abstractmethod
def _add_corners(self, im, rad, multiplier=6):
raise NotImplementedError


class CompositeMetaClass(type(commands.Cog), type(ABC)):
"""
This allows the metaclass used for proper type detection to
coexist with discord.py's metaclass
"""

pass
27 changes: 27 additions & 0 deletions leveler/argparsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import argparse
from shlex import split

from redbot.core import commands


class NoExitParser(argparse.ArgumentParser):
def error(self, message):
raise commands.BadArgument(message)


class TopParser(commands.Converter):
page: int
global_top: bool
rep: bool
server: str

async def convert(self, ctx, argument):
parser = NoExitParser(description="top command arguments parser", add_help=False)
parser.add_argument("page", nargs="?", type=int, default="1")
parser.add_argument("-g", "--global", dest="global_top", action="store_true")
parser.add_argument("-r", "--rep", action="store_true")
parser.add_argument("-s", "--server", "--guild", type=str, nargs="*")
try:
return parser.parse_args(split(argument))
except ValueError as e:
raise commands.BadArgument(*e.args)
21 changes: 21 additions & 0 deletions leveler/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from ..abc import CompositeMetaClass
from .database import DataBase
from .db_converters import DBConverters
from .lvladmin import LevelAdmin
from .lvlset import LevelSet
from .other import Other
from .profiles import Profiles
from .top import Top


class LevelerCommands(
Profiles,
DataBase,
Top,
LevelAdmin,
LevelSet,
DBConverters,
Other,
metaclass=CompositeMetaClass,
):
"""Class joining all command subclasses"""
114 changes: 114 additions & 0 deletions leveler/commands/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import discord
from redbot.core import commands
from redbot.core.utils import chat_formatting as chat
from tabulate import tabulate

from ..abc import CompositeMetaClass, MixinMeta

concurrency = commands.MaxConcurrency(1, per=commands.BucketType.default, wait=False)


def levelerset_concurrency():
"""Custom concurrency pool for levelerset commands"""

def decorator(func):
if isinstance(func, commands.Command):
func._max_concurrency = concurrency
else:
func.__commands_max_concurrency__ = concurrency
return func

return decorator


class DataBase(MixinMeta, metaclass=CompositeMetaClass):
@commands.is_owner()
@commands.group()
async def levelerset(self, ctx):
"""
MongoDB server configuration options.
Use that command in DM to see current settings.
"""
if not ctx.invoked_subcommand and ctx.channel.type == discord.ChannelType.private:
settings = [
(setting.replace("_", " ").title(), value)
for setting, value in (await self.config.custom("MONGODB").get_raw()).items()
if value
]
await ctx.send(chat.box(tabulate(settings, tablefmt="plain")))

@levelerset.command()
@levelerset_concurrency()
async def reconnect(self, ctx):
"""Attempt to reconnect to MongoDB without changing settings"""
message = await ctx.send("Reconnecting...")
client = await self._connect_to_mongo()
if not client:
return await message.edit(content="Failed to connect...")
await message.edit(content="Reconnected.")

@levelerset.command()
@levelerset_concurrency()
async def host(self, ctx, host: str = "localhost"):
"""Set the MongoDB server host."""
await self.config.custom("MONGODB").host.set(host)
message = await ctx.send(
f"MongoDB host set to {host}.\nNow trying to connect to the new host..."
)
client = await self._connect_to_mongo()
if not client:
return await message.edit(
content=message.content.replace("Now trying to connect to the new host...", "")
+ "Failed to connect. Please try again with a valid host."
)
await message.edit(
content=message.content.replace("Now trying to connect to the new host...", "")
)

@levelerset.command()
@levelerset_concurrency()
async def port(self, ctx, port: int = 27017):
"""Set the MongoDB server port."""
await self.config.custom("MONGODB").port.set(port)
message = await ctx.send(
f"MongoDB port set to {port}.\nNow trying to connect to the new port..."
)
client = await self._connect_to_mongo()
if not client:
return await message.edit(
content=message.content.replace("Now trying to connect to the new port...", "")
+ "Failed to connect. Please try again with a valid port."
)
await message.edit(
content=message.content.replace("Now trying to connect to the new port...", "")
)

@levelerset.command(aliases=["creds"])
@levelerset_concurrency()
async def credentials(self, ctx, username: str = None, password: str = None):
"""Set the MongoDB server credentials."""
await self.config.custom("MONGODB").username.set(username)
await self.config.custom("MONGODB").password.set(password)
message = await ctx.send("MongoDB credentials set.\nNow trying to connect...")
client = await self._connect_to_mongo()
if not client:
return await message.edit(
content=message.content.replace("Now trying to connect...", "")
+ "Failed to connect. Please try again with valid credentials."
)
await message.edit(content=message.content.replace("Now trying to connect...", ""))

@levelerset.command()
@levelerset_concurrency()
async def dbname(self, ctx, dbname: str = "leveler"):
"""Set the MongoDB db name."""
await self.config.custom("MONGODB").db_name.set(dbname)
message = await ctx.send("MongoDB db name set.\nNow trying to connect...")
client = await self._connect_to_mongo()
if not client:
return await message.edit(
content=message.content.replace("Now trying to connect...", "")
+ "Failed to connect. Please try again with a valid db name."
)
await message.edit(content=message.content.replace("Now trying to connect...", ""))
7 changes: 7 additions & 0 deletions leveler/commands/db_converters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from leveler.abc import CompositeMetaClass

from .meesix import MeeSix


class DBConverters(MeeSix, metaclass=CompositeMetaClass):
"""Database converters commands"""
10 changes: 10 additions & 0 deletions leveler/commands/db_converters/basecmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from redbot.core import commands

from leveler.abc import CompositeMetaClass


class DBConvertersBaseCMD(metaclass=CompositeMetaClass):
@commands.group()
@commands.is_owner()
async def lvlconvert(self, ctx):
"""Convert levels from other leveling systems."""
Loading

0 comments on commit f88312c

Please sign in to comment.