From 16aeaa50db79e3c17194128a93d22370bf5daa33 Mon Sep 17 00:00:00 2001 From: Arjun Satarkar Date: Tue, 19 Mar 2024 23:07:54 -0400 Subject: markov: avoid float division in uint_to_bytes It seems as though the float division could cause an issue where the actual value should be an integer but the float representation is slightly higher than that, causing the addition of an extra unnecessary byte. Although this works fine as long as the same function is used, it could cause problems if seemingly inconsequential details change. I switch to using divmod and checking for the presence of a remainder. This should ensure use of the exact minimum required number of bytes in the blob. --- markov/markov.py | 31 +++++----- simplestarboard/__init__.py | 5 -- simplestarboard/info.json | 5 -- simplestarboard/starboard.py | 143 ------------------------------------------- starboard/__init__.py | 5 ++ starboard/info.json | 5 ++ starboard/starboard.py | 143 +++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 170 insertions(+), 167 deletions(-) delete mode 100644 simplestarboard/__init__.py delete mode 100644 simplestarboard/info.json delete mode 100644 simplestarboard/starboard.py create mode 100644 starboard/__init__.py create mode 100644 starboard/info.json create mode 100644 starboard/starboard.py diff --git a/markov/markov.py b/markov/markov.py index e206800..73ac08e 100644 --- a/markov/markov.py +++ b/markov/markov.py @@ -151,13 +151,29 @@ class Markov(commands.Cog): def uint_to_bytes(self, x: int): if x < 0: raise ValueError(f"x must be non-negative (got {x})") - return x.to_bytes(math.ceil(x.bit_length() / 8), byteorder="big", signed=False) + byte_length, remainder = divmod(x.bit_length(), 8) + if remainder: + byte_length += 1 + return x.to_bytes(byte_length, byteorder="big", signed=False) def get_base_channel(self, channel_or_thread): if isinstance(channel_or_thread, discord.Thread): return channel_or_thread.parent return channel_or_thread + def append_token(self, text, token): + # NOTE: if changing PUNCTUATION, also change the regex in process_message() with the corresponding note + PUNCTUATION = r".,!?/;()" + if token == "/": + text = text[:-1] + token + elif token == "(": + text += token + elif token in PUNCTUATION: + text = text[:-1] + token + " " + else: + text += token + " " + return text + @commands.group() async def markov(self, _ctx): """ @@ -385,19 +401,6 @@ class Markov(commands.Cog): await db.commit() await ctx.reply("All markov data for this guild has been deleted.") - def append_token(self, text, token): - # NOTE: if changing PUNCTUATION, also change the regex in process_message() with the corresponding note - PUNCTUATION = r".,!?/;()" - if token == "/": - text = text[:-1] + token - elif token == "(": - text += token - elif token in PUNCTUATION: - text = text[:-1] + token + " " - else: - text += token + " " - return text - @markov.command() async def generate(self, ctx, member: discord.Member | None): if not await self.config.guild(ctx.guild).use_messages(): diff --git a/simplestarboard/__init__.py b/simplestarboard/__init__.py deleted file mode 100644 index 26d4ba8..0000000 --- a/simplestarboard/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .starboard import SimpleStarboard - - -async def setup(bot): - await bot.add_cog(SimpleStarboard(bot)) diff --git a/simplestarboard/info.json b/simplestarboard/info.json deleted file mode 100644 index af11ade..0000000 --- a/simplestarboard/info.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "author": ["Arjun Satarkar"], - "description": "Create starboards in your server.", - "short": "Create starboards in your server." -} diff --git a/simplestarboard/starboard.py b/simplestarboard/starboard.py deleted file mode 100644 index 3ae3c1c..0000000 --- a/simplestarboard/starboard.py +++ /dev/null @@ -1,143 +0,0 @@ -import asyncio -import discord -import redbot.core -from redbot.core import Config -from redbot.core import commands - - -class SimpleStarboard(commands.Cog): - def __init__(self, bot): - self.bot = bot - self.config = Config.get_conf( - self, identifier="551742410770612234|31374c1f-a5e9-470a-8fed-1137e980f27a" - ) - self.config.register_guild(starboards={}) - - def emoji_as_sendable_text(self, emoji: discord.Emoji | discord.PartialEmoji | str): - if isinstance(emoji, str): - return emoji - if emoji.animated: - return f"" - else: - return f"<:{emoji.name}:{emoji.id}>" - - @commands.group() - async def simplestarboard(self, _ctx): - pass - - @simplestarboard.command() - @commands.admin_or_permissions(manage_guild=True) - async def add(self, ctx, name: str, channel: discord.TextChannel, threshold: int): - if threshold < 1: - await ctx.reply("Error: threshold must be 1 or higher.") - return - async with self.config.guild(ctx.guild).starboards() as starboards: - if name in starboards: - await ctx.reply( - "Error: a starboard with that name already exists (see `starboard list`)." - ) - return - starboards[name] = { - "channel_id": channel.id, - "threshold": threshold, - "allow_all_reactions": False, - "reactions": { - "unicode": {}, - "custom": {}, - }, - } - - wait_message_id = ( - await ctx.reply( - f"Creating starboard ``{name}`` posting to {channel.mention} requiring {threshold} reactions." - "\nReact to this message with all reactions you want the bot to consider for this starboard," - " then send DONE, or else type ANY to check for all reactions. If you don't react, ⭐ will" - " be chosen by default.", - allowed_mentions=discord.AllowedMentions.none(), - ) - ).id - - try: - done_message = await self.bot.wait_for( - "message", - check=lambda m: m.content.lower() in ["done", "any"], - timeout=120, - ) - except asyncio.TimeoutError: - await ctx.send( - f"Error: timed out; cancelling creation of starboard ``{name}``.", - allowed_mentions=discord.AllowedMentions.none(), - ) - del starboards[name] - return - - # Accumulated to easily post the confirmation message - emoji_text_list = [] - match done_message.content.lower(): - case "done": - reactions = ( - await ctx.channel.fetch_message(wait_message_id) - ).reactions - if not reactions: - starboards[name]["reactions"]["unicode"]["⭐"] = 1 - emoji_text_list.append("⭐") - else: - for reaction in reactions: - emoji = reaction.emoji - if isinstance(emoji, str): - starboards[name]["reactions"]["unicode"][emoji] = 1 - else: - starboards[name]["reactions"]["custom"][emoji.id] = 1 - emoji_text_list.append(self.emoji_as_sendable_text(emoji)) - case "any": - starboards[name]["allow_all_reactions"] = True - - confirmation_text = f"Created starboard ``{name}`` posting to {channel.mention} and requiring {threshold}" - if not emoji_text_list: - confirmation_text += " of any emoji." - else: - confirmation_text += f" reactions of {redbot.core.utils.chat_formatting.humanize_list(emoji_text_list, style='or')}." - await ctx.send( - confirmation_text, allowed_mentions=discord.AllowedMentions.none() - ) - - @simplestarboard.command() - @commands.admin_or_permissions(manage_guild=True) - async def remove(self, ctx, name: str): - async with self.config.guild(ctx.guild).starboards() as starboards: - try: - del starboards[name] - except KeyError: - await ctx.reply( - "Error: no starboard found with that name (see `starboard list`)." - ) - else: - await ctx.reply("Removed that starboard.") - - @simplestarboard.command() - async def list(self, ctx): - starboards = await self.config.guild(ctx.guild).starboards() - list_text = "Name, Channel, Threshold, Reactions" - for name in starboards: - starboard = starboards[name] - list_text += ( - f"\n* ``{name}``, {ctx.guild.get_channel(starboard['channel_id']).mention}," - f" {starboard['threshold']}, " - ) - if starboard["allow_all_reactions"]: - list_text += "*any*" - else: - emoji_list = [] - for emoji in starboard["reactions"]["custom"]: - emoji_list.append( - self.emoji_as_sendable_text(self.bot.get_emoji(int(emoji))) - ) - for emoji in starboard["reactions"]["unicode"]: - emoji_list.append(emoji) - list_text += redbot.core.utils.chat_formatting.humanize_list(emoji_list) - pages = [ - *redbot.core.utils.chat_formatting.pagify( - discord.utils.escape_mentions(list_text) - ) - ] - await redbot.core.utils.menus.menu(ctx, pages) diff --git a/starboard/__init__.py b/starboard/__init__.py new file mode 100644 index 0000000..eeb3d75 --- /dev/null +++ b/starboard/__init__.py @@ -0,0 +1,5 @@ +from .starboard import Starboard + + +async def setup(bot): + await bot.add_cog(Starboard(bot)) diff --git a/starboard/info.json b/starboard/info.json new file mode 100644 index 0000000..af11ade --- /dev/null +++ b/starboard/info.json @@ -0,0 +1,5 @@ +{ + "author": ["Arjun Satarkar"], + "description": "Create starboards in your server.", + "short": "Create starboards in your server." +} diff --git a/starboard/starboard.py b/starboard/starboard.py new file mode 100644 index 0000000..751c7c3 --- /dev/null +++ b/starboard/starboard.py @@ -0,0 +1,143 @@ +import asyncio +import discord +import redbot.core +from redbot.core import Config +from redbot.core import commands + + +class Starboard(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.config = Config.get_conf( + self, identifier="551742410770612234|31374c1f-a5e9-470a-8fed-1137e980f27a" + ) + self.config.register_guild(starboards={}) + + def emoji_as_sendable_text(self, emoji: discord.Emoji | discord.PartialEmoji | str): + if isinstance(emoji, str): + return emoji + if emoji.animated: + return f"" + else: + return f"<:{emoji.name}:{emoji.id}>" + + @commands.group() + async def starboard(self, _ctx): + pass + + @starboard.command() + @commands.admin_or_permissions(manage_guild=True) + async def add(self, ctx, name: str, channel: discord.TextChannel, threshold: int): + if threshold < 1: + await ctx.reply("Error: threshold must be 1 or higher.") + return + async with self.config.guild(ctx.guild).starboards() as starboards: + if name in starboards: + await ctx.reply( + "Error: a starboard with that name already exists (see `starboard list`)." + ) + return + starboards[name] = { + "channel_id": channel.id, + "threshold": threshold, + "allow_all_reactions": False, + "reactions": { + "unicode": {}, + "custom": {}, + }, + } + + wait_message_id = ( + await ctx.reply( + f"Creating starboard ``{name}`` posting to {channel.mention} requiring {threshold} reactions." + "\nReact to this message with all reactions you want the bot to consider for this starboard," + " then send DONE, or else type ANY to check for all reactions. If you don't react, ⭐ will" + " be chosen by default.", + allowed_mentions=discord.AllowedMentions.none(), + ) + ).id + + try: + done_message = await self.bot.wait_for( + "message", + check=lambda m: m.content.lower() in ["done", "any"], + timeout=120, + ) + except asyncio.TimeoutError: + await ctx.send( + f"Error: timed out; cancelling creation of starboard ``{name}``.", + allowed_mentions=discord.AllowedMentions.none(), + ) + del starboards[name] + return + + # Accumulated to easily post the confirmation message + emoji_text_list = [] + match done_message.content.lower(): + case "done": + reactions = ( + await ctx.channel.fetch_message(wait_message_id) + ).reactions + if not reactions: + starboards[name]["reactions"]["unicode"]["⭐"] = 1 + emoji_text_list.append("⭐") + else: + for reaction in reactions: + emoji = reaction.emoji + if isinstance(emoji, str): + starboards[name]["reactions"]["unicode"][emoji] = 1 + else: + starboards[name]["reactions"]["custom"][emoji.id] = 1 + emoji_text_list.append(self.emoji_as_sendable_text(emoji)) + case "any": + starboards[name]["allow_all_reactions"] = True + + confirmation_text = f"Created starboard ``{name}`` posting to {channel.mention} and requiring {threshold}" + if not emoji_text_list: + confirmation_text += " of any emoji." + else: + confirmation_text += f" reactions of {redbot.core.utils.chat_formatting.humanize_list(emoji_text_list, style='or')}." + await ctx.send( + confirmation_text, allowed_mentions=discord.AllowedMentions.none() + ) + + @starboard.command() + @commands.admin_or_permissions(manage_guild=True) + async def remove(self, ctx, name: str): + async with self.config.guild(ctx.guild).starboards() as starboards: + try: + del starboards[name] + except KeyError: + await ctx.reply( + "Error: no starboard found with that name (see `starboard list`)." + ) + else: + await ctx.reply("Removed that starboard.") + + @starboard.command() + async def list(self, ctx): + starboards = await self.config.guild(ctx.guild).starboards() + list_text = "Name, Channel, Threshold, Reactions" + for name in starboards: + starboard = starboards[name] + list_text += ( + f"\n* ``{name}``, {ctx.guild.get_channel(starboard['channel_id']).mention}," + f" {starboard['threshold']}, " + ) + if starboard["allow_all_reactions"]: + list_text += "*any*" + else: + emoji_list = [] + for emoji in starboard["reactions"]["custom"]: + emoji_list.append( + self.emoji_as_sendable_text(self.bot.get_emoji(int(emoji))) + ) + for emoji in starboard["reactions"]["unicode"]: + emoji_list.append(emoji) + list_text += redbot.core.utils.chat_formatting.humanize_list(emoji_list) + pages = [ + *redbot.core.utils.chat_formatting.pagify( + discord.utils.escape_mentions(list_text) + ) + ] + await redbot.core.utils.menus.menu(ctx, pages) -- cgit v1.2.3-57-g22cb