diff --git a/sizebot/cogs/admin.py b/sizebot/cogs/admin.py index 14543170..b1fba6ed 100644 --- a/sizebot/cogs/admin.py +++ b/sizebot/cogs/admin.py @@ -5,7 +5,7 @@ from discord.ext import commands from sizebot.lib import userdb -from sizebot.lib.types import BotContext +from sizebot.lib.types import GuildContext logger = logging.getLogger("sizebot") @@ -19,7 +19,7 @@ def __init__(self, bot: commands.Bot): hidden = True ) @commands.is_owner() - async def halt(self, ctx: BotContext): + async def halt(self, ctx: GuildContext): """RIP SizeBot.""" logger.critical(f"Help, {ctx.author.display_name} is closing me!") await ctx.send("Stopping SizeBot. ☠️") @@ -29,7 +29,7 @@ async def halt(self, ctx: BotContext): hidden = True ) @commands.is_owner() - async def dump(self, ctx: BotContext, *, user: discord.Member = None): + async def dump(self, ctx: GuildContext, *, user: discord.Member = None): """Dump a user's data.""" if user is None: user = ctx.author @@ -40,7 +40,7 @@ async def dump(self, ctx: BotContext, *, user: discord.Member = None): hidden = True ) @commands.is_owner() - async def sudo(self, ctx: BotContext, victim: discord.Member, *, command: str): + async def sudo(self, ctx: GuildContext, victim: discord.Member, *, command: str): """Take control.""" logger.warn(f"{ctx.author.display_name} made {victim.display_name} run {command}.") new_message = copy(ctx.message) diff --git a/sizebot/cogs/change.py b/sizebot/cogs/change.py index b7c3085f..d782ea2b 100644 --- a/sizebot/cogs/change.py +++ b/sizebot/cogs/change.py @@ -1,7 +1,7 @@ import importlib.resources as pkg_resources import logging import random -from typing import cast +from typing import Self, cast from sizebot.lib import errors, utils from discord import Member @@ -12,7 +12,7 @@ from sizebot.lib.diff import Diff, LimitedRate, Rate from sizebot.lib.errors import ChangeMethodInvalidException from sizebot.lib.objs import DigiObject, objects -from sizebot.lib.types import BotContext, StrToSend +from sizebot.lib.types import BotContext, GuildContext, StrToSend from sizebot.lib.units import SV, Decimal logger = logging.getLogger("sizebot") @@ -28,7 +28,7 @@ async def on_first_ready(self): # Don't start the change tasks until the bot is properly connected self.changeTask.start() - def cog_unload(self): + def cog_unload(self): # type: ignore (Bad typing in discord.py) self.changeTask.cancel() @commands.command( @@ -36,7 +36,8 @@ def cog_unload(self): category = "change", usage = " [rate] [stop]" ) - async def change(self, ctx: BotContext, *, arg: LimitedRate | Rate | Diff | str): + @commands.guild_only() + async def change(self, ctx: GuildContext, *, arg: LimitedRate | Rate | Diff | str): """Either change or slow-change your height. Can be used in essentially the three following ways: @@ -66,7 +67,7 @@ async def change(self, ctx: BotContext, *, arg: LimitedRate | Rate | Diff | str) elif style == "power": userdata.scale = userdata.scale ** cast(Decimal, amount) else: - raise ChangeMethodInvalidException + raise ChangeMethodInvalidException(style) await nickmanager.nick_update(ctx.author) userdb.save(userdata) await ctx.send(f"{userdata.nickname} is now {userdata.height:m} ({userdata.height:u}) tall.") @@ -96,7 +97,7 @@ async def changes(self, ctx: BotContext): category = "change" ) @commands.guild_only() - async def stopchange(self, ctx: BotContext): + async def stopchange(self, ctx: GuildContext): """Stop a currently active slow change.""" await ctx.send(**stop_changes(ctx.author)) @@ -105,7 +106,7 @@ async def stopchange(self, ctx: BotContext): category = "change" ) @commands.guild_only() - async def eatme(self, ctx: BotContext): + async def eatme(self, ctx: GuildContext): """Eat me! Increases your height by a random amount between 2x and 20x.""" @@ -113,8 +114,8 @@ async def eatme(self, ctx: BotContext): userid = ctx.author.id userdata = userdb.load(guildid, userid) - randmult = round(random.randint(2, 20), 1) - change_user(guildid, userid, "multiply", randmult) + randmult = Decimal(random.randint(2, 20)) + change_user_mul(guildid, ctx.author.id, randmult) await nickmanager.nick_update(ctx.author) userdata = userdb.load(guildid, userid) @@ -130,7 +131,7 @@ async def eatme(self, ctx: BotContext): category = "change" ) @commands.guild_only() - async def drinkme(self, ctx: BotContext): + async def drinkme(self, ctx: GuildContext): """Drink me! Decreases your height by a random amount between 2x and 20x.""" @@ -138,8 +139,8 @@ async def drinkme(self, ctx: BotContext): userid = ctx.author.id userdata = userdb.load(guildid, userid) - randmult = round(random.randint(2, 20), 1) - change_user(guildid, ctx.author.id, "divide", randmult) + randmult = Decimal(random.randint(2, 20)) + change_user_div(guildid, ctx.author.id, randmult) await nickmanager.nick_update(ctx.author) userdata = userdb.load(guildid, userid) @@ -154,21 +155,21 @@ async def drinkme(self, ctx: BotContext): category = "change" ) @commands.guild_only() - async def pushme(self, ctx: BotContext): + async def pushme(self, ctx: GuildContext): """Push me! Increases or decreases your height by a random amount between 2x and 20x.""" - c = random.randint(1, 2) - if c == 1: - await ctx.invoke(self.bot.get_command("eatme")) - else: - await ctx.invoke(self.bot.get_command("drinkme")) + next_cmd_name = random.choice(["eatme", "drinkme"]) + next_cmd = cast(commands.Command[Self, ..., None] | None, self.bot.get_command(next_cmd_name)) + if next_cmd is None: + raise errors.ThisShouldNeverHappenException(f"Missing command: {next_cmd_name}") + await ctx.invoke(next_cmd) @commands.command( category = "change" ) @commands.guild_only() - async def outgrow(self, ctx: BotContext, *, obj: DigiObject = None): + async def outgrow(self, ctx: GuildContext, *, obj: DigiObject | None = None): """Outgrows the next object in the object database, or an object you specify.""" guildid = ctx.guild.id userid = ctx.author.id @@ -186,7 +187,7 @@ async def outgrow(self, ctx: BotContext, *, obj: DigiObject = None): return random_factor = Decimal(random.randint(11, 20) / 10) - userdata.height = SV(obj.unitlength * random_factor) + userdata.height = obj.unitlength * random_factor userdb.save(userdata) await ctx.send(f"You outgrew {obj.article} **{obj.name}** *({obj.unitlength:,.3mu})* and are now **{userdata.height:,.3mu}** tall!") @@ -195,7 +196,7 @@ async def outgrow(self, ctx: BotContext, *, obj: DigiObject = None): category = "change" ) @commands.guild_only() - async def outshrink(self, ctx: BotContext, *, obj: DigiObject = None): + async def outshrink(self, ctx: GuildContext, *, obj: DigiObject | None = None): """Outshrinks the next object in the object database or an object you specify.""" guildid = ctx.guild.id userid = ctx.author.id @@ -214,7 +215,7 @@ async def outshrink(self, ctx: BotContext, *, obj: DigiObject = None): return random_factor = Decimal(random.randint(11, 20) / 10) - userdata.height = SV(obj.unitlength / random_factor) + userdata.height = obj.unitlength / random_factor userdb.save(userdata) await ctx.send(f"You outshrunk {obj.article} **{obj.name}** *({obj.unitlength:,.3mu})* and are now **{userdata.height:,.3mu}** tall!") @@ -230,59 +231,23 @@ async def changeTask(self): logger.error(utils.format_traceback(e)) -def change_user(guildid: int, userid: int, changestyle: str, amount: SV): - changestyle = changestyle.lower() - if changestyle in ["add", "+", "a", "plus"]: - changestyle = "add" - if changestyle in ["subtract", "sub", "-", "minus"]: - changestyle = "subtract" - if changestyle in ["power", "exp", "pow", "exponent", "^", "**"]: - changestyle = "power" - if changestyle in ["multiply", "mult", "m", "x", "times", "*"]: - changestyle = "multiply" - if changestyle in ["divide", "d", "/", "div"]: - changestyle = "divide" - if changestyle in ["percent", "per", "perc", "%"]: - changestyle = "percent" - - if changestyle not in ["add", "subtract", "multiply", "divide", "power", "percent"]: - raise errors.ChangeMethodInvalidException(changestyle) - - amountSV = None - amountVal = None - newamount = None - - if changestyle in ["add", "subtract"]: - amountSV = SV.parse(amount) - elif changestyle in ["multiply", "divide", "power"]: - amountVal = Decimal(amount) - if amountVal == 1: - raise errors.ValueIsOneException - if amountVal == 0: - raise errors.ValueIsZeroException - elif changestyle in ["percent"]: - amountVal = Decimal(amount) - if amountVal == 0: - raise errors.ValueIsZeroException - +def change_user_mul(guildid: int, userid: int, amount: Decimal): + if amount == 1: + raise errors.ValueIsOneException + if amount == 0: + raise errors.ValueIsZeroException userdata = userdb.load(guildid, userid) + userdata.height = userdata.height * amount + userdb.save(userdata) - if changestyle == "add": - newamount = userdata.height + amountSV - elif changestyle == "subtract": - newamount = userdata.height - amountSV - elif changestyle == "multiply": - newamount = userdata.height * amountVal - elif changestyle == "divide": - newamount = userdata.height / amountVal - elif changestyle == "power": - userdata = userdata ** amountVal - elif changestyle == "percent": - newamount = userdata.height * (amountVal / 100) - - if changestyle != "power": - userdata.height = newamount +def change_user_div(guildid: int, userid: int, amount: Decimal): + if amount == 1: + raise errors.ValueIsOneException + if amount == 0: + raise errors.ValueIsZeroException + userdata = userdb.load(guildid, userid) + userdata.height = userdata.height / amount userdb.save(userdata) diff --git a/sizebot/cogs/edge.py b/sizebot/cogs/edge.py index f79d994b..f1dbf719 100644 --- a/sizebot/cogs/edge.py +++ b/sizebot/cogs/edge.py @@ -11,7 +11,7 @@ from sizebot.lib import guilddb, userdb, nickmanager from sizebot.lib.checks import is_mod -from sizebot.lib.types import BotContext +from sizebot.lib.types import GuildContext from sizebot.lib.units import SV, Decimal logger = logging.getLogger("sizebot") @@ -60,7 +60,8 @@ def __init__(self, bot: commands.Bot): hidden = True ) @is_mod() - async def edges(self, ctx: BotContext): + @commands.guild_only() + async def edges(self, ctx: GuildContext): """See who is set to be the smallest and largest users.""" guilddata = guilddb.load_or_create(ctx.guild.id) await ctx.send(f"**SERVER-SET SMALLEST AND LARGEST USERS:**\nSmallest: {'*Unset*' if guilddata.small_edge is None else guilddata.small_edge}\nLargest: {'*Unset*' if guilddata.large_edge is None else guilddata.large_edge}") @@ -72,7 +73,8 @@ async def edges(self, ctx: BotContext): category = "mod" ) @is_mod() - async def setsmallest(self, ctx: BotContext, *, member: discord.Member): + @commands.guild_only() + async def setsmallest(self, ctx: GuildContext, *, member: discord.Member): """Set the smallest user.""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.small_edge = member.id @@ -87,7 +89,8 @@ async def setsmallest(self, ctx: BotContext, *, member: discord.Member): category = "mod" ) @is_mod() - async def setlargest(self, ctx: BotContext, *, member: discord.Member): + @commands.guild_only() + async def setlargest(self, ctx: GuildContext, *, member: discord.Member): """Set the largest user.""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.large_edge = member.id @@ -101,7 +104,8 @@ async def setlargest(self, ctx: BotContext, *, member: discord.Member): category = "mod" ) @is_mod() - async def clearsmallest(self, ctx: BotContext): + @commands.guild_only() + async def clearsmallest(self, ctx: GuildContext): """Clear the role of 'smallest user.'""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.small_edge = None @@ -115,7 +119,8 @@ async def clearsmallest(self, ctx: BotContext): category = "mod" ) @is_mod() - async def clearlargest(self, ctx: BotContext): + @commands.guild_only() + async def clearlargest(self, ctx: GuildContext): """Clear the role of 'largest user.'""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.large_edge = None @@ -128,7 +133,8 @@ async def clearlargest(self, ctx: BotContext): category = "mod" ) @is_mod() - async def edgedebug(self, ctx: BotContext): + @commands.guild_only() + async def edgedebug(self, ctx: GuildContext): userdata = userdb.load(ctx.guild.id, ctx.author.id) usersizes = getUserSizes(ctx.guild) guilddata = guilddb.load(ctx.guild.id) diff --git a/sizebot/cogs/fun.py b/sizebot/cogs/fun.py index 74755fc5..8a515e4f 100644 --- a/sizebot/cogs/fun.py +++ b/sizebot/cogs/fun.py @@ -9,7 +9,7 @@ from sizebot.lib import userdb from sizebot.lib.constants import ids from sizebot.lib.loglevels import EGG -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext tasks = {} @@ -36,7 +36,8 @@ async def sbsay(self, ctx: BotContext, *, message: str): aliases = ["tra"], category = "fun" ) - async def report(self, ctx: BotContext, *, user: discord.User): + @commands.guild_only() + async def report(self, ctx: GuildContext, *, user: discord.User): """Report a user to the Tiny Rights Alliance.""" ud = userdb.load(ctx.guild.id, user.id) ud.tra_reports += 1 diff --git a/sizebot/cogs/holiday.py b/sizebot/cogs/holiday.py index ca74b595..a3e0d964 100644 --- a/sizebot/cogs/holiday.py +++ b/sizebot/cogs/holiday.py @@ -27,7 +27,7 @@ def __init__(self, bot: commands.Bot): self.bot = bot self.holidayTask.start() - def cog_unload(self): + def cog_unload(self): # type: ignore (Bad typing in discord.py) self.holidayTask.cancel() # TODO: CamelCase diff --git a/sizebot/cogs/limits.py b/sizebot/cogs/limits.py index 34afdf7f..28bd0582 100644 --- a/sizebot/cogs/limits.py +++ b/sizebot/cogs/limits.py @@ -6,7 +6,7 @@ from sizebot.lib import guilddb, userdb, nickmanager from sizebot.lib.checks import is_mod -from sizebot.lib.types import BotContext +from sizebot.lib.types import GuildContext from sizebot.lib.units import SV logger = logging.getLogger("sizebot") @@ -21,7 +21,8 @@ def __init__(self, bot: commands.Bot): @commands.command( category = "misc" ) - async def limits(self, ctx: BotContext): + @commands.guild_only() + async def limits(self, ctx: GuildContext): """See the guild's current caps.""" guilddata = guilddb.load_or_create(ctx.guild.id) await ctx.send(f"**SERVER-SET LOW CAPS AND HIGH CAPS:**\nLow Limit: {'*Unset*' if guilddata.low_limit is None else guilddata.low_limit:,.3mu}\nHigh Limit: {'*Unset*' if guilddata.high_limit is None else guilddata.high_limit:,.3mu}") @@ -33,7 +34,8 @@ async def limits(self, ctx: BotContext): category = "mod" ) @is_mod() - async def setlowlimit(self, ctx: BotContext, *, size: SV): + @commands.guild_only() + async def setlowlimit(self, ctx: GuildContext, *, size: SV): """Set the low size limit (floor).""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.low_limit = size @@ -48,7 +50,8 @@ async def setlowlimit(self, ctx: BotContext, *, size: SV): category = "mod" ) @is_mod() - async def sethighlimit(self, ctx: BotContext, *, size: SV): + @commands.guild_only() + async def sethighlimit(self, ctx: GuildContext, *, size: SV): """Set the high size limit (ceiling).""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.high_limit = size @@ -63,7 +66,8 @@ async def sethighlimit(self, ctx: BotContext, *, size: SV): category = "mod" ) @is_mod() - async def clearlowlimit(self, ctx: BotContext): + @commands.guild_only() + async def clearlowlimit(self, ctx: GuildContext): """Set the low size limit (floor).""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.low_limit = None @@ -78,7 +82,8 @@ async def clearlowlimit(self, ctx: BotContext): category = "mod" ) @is_mod() - async def clearhighlimit(self, ctx: BotContext): + @commands.guild_only() + async def clearhighlimit(self, ctx: GuildContext): """Set the high size limit (ceiling).""" guilddata = guilddb.load_or_create(ctx.guild.id) guilddata.high_limit = None diff --git a/sizebot/cogs/loop.py b/sizebot/cogs/loop.py index 644e151a..d7218e44 100644 --- a/sizebot/cogs/loop.py +++ b/sizebot/cogs/loop.py @@ -8,10 +8,10 @@ from discord.ext import commands from sizebot.lib.stats import StatBox -from sizebot.lib.units import SV, TV +from sizebot.lib.units import RV, SV, TV from sizebot.lib import userdb from sizebot.lib.constants import emojis -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.utils import pretty_time_delta from sizebot.lib.userdb import MoveTypeStr import sizebot.lib.language as lang @@ -36,8 +36,8 @@ def calc_move_dist(userdata: userdb.User) -> tuple[TV, SV]: except KeyError: raise ValueError(f"{movetype}perhour is not a valid stat.") - persecond = SV(speed / 60 / 60) - distance = SV(elapsed_seconds * persecond) + persecond = RV(speed / 60 / 60) + distance = elapsed_seconds * persecond) return elapsed_seconds, distance @@ -51,7 +51,7 @@ def __init__(self, bot: commands.Bot): category = "loop" ) @commands.guild_only() - async def start(self, ctx: BotContext, action: str, stop: TV = None): + async def start(self, ctx: GuildContext, action: str, stop: TV = None): """Keep moving forward -- Walt Disney `` can be one of the following: walk, run, climb, crawl, swim @@ -87,7 +87,7 @@ async def start(self, ctx: BotContext, action: str, stop: TV = None): category = "loop" ) @commands.guild_only() - async def stop(self, ctx: BotContext): + async def stop(self, ctx: GuildContext): """Stop a current movement.""" userdata = userdb.load(ctx.guild.id, ctx.author.id) if userdata.currentmovetype is None: @@ -107,7 +107,7 @@ async def stop(self, ctx: BotContext): category = "loop" ) @commands.guild_only() - async def sofar(self, ctx: BotContext, *, who: discord.Member | None = None): + async def sofar(self, ctx: GuildContext, *, who: discord.Member | None = None): """How far have you moved so far? [See help.] #ALPHA# diff --git a/sizebot/cogs/multiplayer.py b/sizebot/cogs/multiplayer.py index 4802f823..8b27d859 100644 --- a/sizebot/cogs/multiplayer.py +++ b/sizebot/cogs/multiplayer.py @@ -8,7 +8,7 @@ from sizebot.lib.constants import colors, emojis from sizebot.lib.diff import Diff from sizebot.lib.errors import ChangeMethodInvalidException -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, WV, Decimal logger = logging.getLogger("sizebot") @@ -24,7 +24,8 @@ def __init__(self, bot: commands.Bot): aliases = ["pb"], category = "multiplayer" ) - async def pushbutton(self, ctx: BotContext, user: discord.Member): + @commands.guild_only() + async def pushbutton(self, ctx: GuildContext, user: discord.Member): """Push someone's button! If a user has a button set (with `&setbutton`,) changes that user by their set amount. @@ -48,7 +49,8 @@ async def pushbutton(self, ctx: BotContext, user: discord.Member): usage = "", category = "multiplayer" ) - async def setbutton(self, ctx: BotContext, *, diff: Diff): + @commands.guild_only() + async def setbutton(self, ctx: GuildContext, *, diff: Diff): """Set up a button for others to push! Set a change amount, and when others run `&pushbutton` on you, you'll change by that amount. @@ -62,7 +64,8 @@ async def setbutton(self, ctx: BotContext, *, diff: Diff): category = "multiplayer", aliases = ["resetbutton", "unsetbutton", "removebutton"] ) - async def clearbutton(self, ctx: BotContext): + @commands.guild_only() + async def clearbutton(self, ctx: GuildContext): """Remove your push button.""" userdata = userdb.load(ctx.guild.id, ctx.author.id) userdata.button = None @@ -73,7 +76,8 @@ async def clearbutton(self, ctx: BotContext): usage = " [thief]", category = "multiplayer" ) - async def steal(self, ctx: BotContext, amount: SV | WV, victim: discord.Member, thief: discord.Member = None): + @commands.guild_only() + async def steal(self, ctx: GuildContext, amount: SV | WV, victim: discord.Member, thief: discord.Member = None): """See what would happen if you stole size from a user. `amount` can be a height amount or a weight amount. @@ -145,7 +149,8 @@ async def steal(self, ctx: BotContext, amount: SV | WV, victim: discord.Member, category = "multiplayer", usage = " " ) - async def changeother(self, ctx: BotContext, other: discord.Member, *, string: Diff): + @commands.guild_only() + async def changeother(self, ctx: GuildContext, other: discord.Member, *, string: Diff): """Change someone else's height. The other user must have this functionality enabled.""" userdata = userdb.load(other.guild.id, other.id) @@ -176,7 +181,7 @@ async def changeother(self, ctx: BotContext, other: discord.Member, *, string: D category = "multiplayer" ) @commands.guild_only() - async def setother(self, ctx: BotContext, other: discord.Member, *, newheight: SV): + async def setother(self, ctx: GuildContext, other: discord.Member, *, newheight: SV): """Set someone else's height. The other user must have this functionality enabled.""" userdata = userdb.load(other.guild.id, other.id) @@ -194,7 +199,8 @@ async def setother(self, ctx: BotContext, other: discord.Member, *, newheight: S @commands.command( category = "multiplayer" ) - async def toggleallowothers(self, ctx: BotContext): + @commands.guild_only() + async def toggleallowothers(self, ctx: GuildContext): """Allow other users to change your size. NOTE: THIS HAS NO WHITELIST, BLACKLIST, LIMITS, OR OTHERWISE. diff --git a/sizebot/cogs/naptime.py b/sizebot/cogs/naptime.py index c4913356..45b00402 100644 --- a/sizebot/cogs/naptime.py +++ b/sizebot/cogs/naptime.py @@ -3,7 +3,7 @@ from discord.ext import commands, tasks from sizebot.lib import naps, utils -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import TV logger = logging.getLogger("sizebot") @@ -16,7 +16,7 @@ def __init__(self, bot: commands.Bot): self.bot = bot self.nannyTask.start() - def cog_unload(self): + def cog_unload(self): # type: ignore (Bad typing in discord.py) self.nannyTask.cancel() @commands.command( @@ -25,7 +25,7 @@ def cog_unload(self): category = "fun" ) @commands.guild_only() - async def naptime(self, ctx: BotContext, *, duration: TV): + async def naptime(self, ctx: GuildContext, *, duration: TV): """Go to bed in a set amount of time. Kicks you from any voice channel you're in after a set amount of time. diff --git a/sizebot/cogs/objects.py b/sizebot/cogs/objects.py index d4bf70f2..b6d93f43 100644 --- a/sizebot/cogs/objects.py +++ b/sizebot/cogs/objects.py @@ -2,7 +2,7 @@ import logging import math import random -from typing import get_args +from typing import cast, get_args import discord from discord import Embed @@ -14,9 +14,9 @@ from sizebot.lib.constants import emojis from sizebot.lib.errors import InvalidSizeValue from sizebot.lib.loglevels import EGG -from sizebot.lib.objs import DigiObject, objects, tags, format_close_object_smart +from sizebot.lib.objs import DigiLand, DigiObject, objects, relativestatsembed, relativestatssentence, statsembed, tags, format_close_object_smart, get_stats_sentence from sizebot.lib.stats import StatBox, taglist, AVERAGE_HEIGHT -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, WV, AV from sizebot.lib.userdb import load_or_fake, MemberOrFake, MemberOrFakeOrSize from sizebot.lib.fakeplayer import FakePlayer @@ -61,7 +61,7 @@ async def objs(self, ctx: BotContext, tag: str = None): category = "objects" ) @commands.guild_only() - async def lookslike(self, ctx: BotContext, *, memberOrHeight: MemberOrFakeOrSize = None): + async def lookslike(self, ctx: GuildContext, *, memberOrHeight: MemberOrFakeOrSize = None): """See how tall you are in comparison to an object.""" if memberOrHeight is None: memberOrHeight = ctx.author @@ -83,7 +83,7 @@ async def lookslike(self, ctx: BotContext, *, memberOrHeight: MemberOrFakeOrSize category = "objects" ) @commands.guild_only() - async def objectcompare(self, ctx: BotContext, *, args: str): + async def objectcompare(self, ctx: GuildContext, *, args: str): """See what an object looks like to you. Used to see how an object would look at your scale. @@ -113,7 +113,7 @@ async def objectcompare(self, ctx: BotContext, *, args: str): userstats = StatBox.load(userdata.stats).scale(userdata.scale) if isinstance(what, DigiObject): - oc = what.relativestatsembed(userdata) + oc = relativestatsembed(what, userdata) await ctx.send(embed = oc) return elif isinstance(what, discord.Member) or isinstance(what, SV): # TODO: Make this not literally just a compare. (make one sided) @@ -133,7 +133,7 @@ async def objectcompare(self, ctx: BotContext, *, args: str): category = "objects" ) @commands.guild_only() - async def lookat(self, ctx: BotContext, *, what: DigiObject | MemberOrFake | SV | str): + async def lookat(self, ctx: GuildContext, *, what: DigiObject | MemberOrFake | SV | str): """See what an object looks like to you. Used to see how an object would look at your scale. @@ -151,7 +151,7 @@ async def lookat(self, ctx: BotContext, *, what: DigiObject | MemberOrFake | SV # Compare to registered DigiObject by string name if isinstance(what, DigiObject): - la = what.relativestatssentence(userdata) + la = relativestatssentence(what, userdata) # Easter eggs. eggs = { "photograph": ("", f"{ctx.author.display_name} is jamming to Nickleback."), @@ -171,7 +171,7 @@ async def lookat(self, ctx: BotContext, *, what: DigiObject | MemberOrFake | SV # Height comparisons if isinstance(what, SV): - height = SV(what * userdata.viewscale) + height = what * userdata.viewscale s = (f"{userdata.nickname} is {userdata.height:,.1{userdata.unitsystem}} tall." f" To them, {what:,.1mu} looks like **{height:,.1mu}**." f" That's about **{height.to_best_unit('o', preferName=True, spec='.1')}**.") @@ -248,14 +248,15 @@ async def objstats(self, ctx: BotContext, *, what: DigiObject | str): await ctx.send(f"`{what}` is not a valid object.") return - await ctx.send(embed = what.statsembed()) + await ctx.send(embed = statsembed(what)) @commands.command( category = "objects", usage = "[@User]" ) # TODO: Bad name. - async def stackup(self, ctx: BotContext, amount: int | None = None, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def stackup(self, ctx: GuildContext, amount: int | None = None, *, who: MemberOrFakeOrSize = None): """How do you stack up against objects? Example: @@ -287,7 +288,8 @@ async def stackup(self, ctx: BotContext, amount: int | None = None, *, who: Memb @commands.command( category = "objects" ) - async def food(self, ctx: BotContext, food: DigiObject | str, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def food(self, ctx: GuildContext, food: DigiObject | str, *, who: MemberOrFakeOrSize = None): """How much food does a person need to eat? Takes optional argument of a user to get the food for. @@ -349,7 +351,8 @@ async def food(self, ctx: BotContext, food: DigiObject | str, *, who: MemberOrFa @commands.command( category = "objects" ) - async def water(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def water(self, ctx: GuildContext, *, who: MemberOrFakeOrSize = None): if who is None: who = ctx.author @@ -371,7 +374,8 @@ async def water(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): @commands.command( category = "objects" ) - async def land(self, ctx: BotContext, land: DigiObject | str, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def land(self, ctx: GuildContext, land: DigiObject | str, *, who: MemberOrFakeOrSize = None): """Get stats about how you cover land. #ACC# @@ -381,8 +385,6 @@ async def land(self, ctx: BotContext, land: DigiObject | str, *, who: MemberOrFa `&land @User random` `&land @User Australia`""" - lands = objs.land - # Input validation. if isinstance(land, DigiObject) and "land" not in land.tags: await ctx.send(f"{emojis.error} `{land.name}` is not land.") @@ -396,17 +398,17 @@ async def land(self, ctx: BotContext, land: DigiObject | str, *, who: MemberOrFa scale = userdata.scale if land == "random": - land = random.choice(lands) + land = random.choice(objs.land) if not isinstance(land, DigiObject): raise InvalidSizeValue(land, "object") - - land_width = SV(land.width / scale) - land_length = SV(land.length / scale) - land_height = SV(land.height / scale) + _land = DigiLand.from_digiobject(land) + land_width = _land.width / scale + land_length = _land.length / scale + land_height = _land.height / scale fingertip_name = "paw bean" if userdata.pawtoggle else "fingertip" - land_area = AV(land.width * land.height) + land_area = _land.area area = AV(stats['height'].value * stats['width'].value) lay_percentage = area / land_area foot_area = AV(stats['footlength'].value * stats['footwidth'].value) @@ -414,7 +416,7 @@ async def land(self, ctx: BotContext, land: DigiObject | str, *, who: MemberOrFa foot_percentage = foot_area / land_area finger_percentage = finger_area / land_area - landout = (f"To {userdata.nickname}, {land.name} looks **{land_width:,.1mu}** wide and **{land_length:,.1mu}** long. ({land_area:,.1mu}) The highest peak looks **{land_height:,.1mu}** tall. ({land.note})\n\n" + landout = (f"To {userdata.nickname}, {_land.name} looks **{land_width:,.1mu}** wide and **{land_length:,.1mu}** long. ({land_area:,.1mu}) The highest peak looks **{land_height:,.1mu}** tall. ({_land.note})\n\n" f"Laying down, {userdata.nickname} would cover **{lay_percentage:,.2}%** of the land.\n" f"{emojis.blank}({stats['height'].value:,.1mu} tall and {stats['width'].value:,.1mu} wide, or {area:,.1mu})\n" f"{userdata.nickname}'s {userdata.footname.lower()} would cover **{foot_percentage:,.2}%** of the land.\n" @@ -455,9 +457,10 @@ async def tags(self, ctx: BotContext): usage = "[object]", category = "objects" ) - async def scaled(self, ctx: BotContext, *, obj: DigiObject): + @commands.guild_only() + async def scaled(self, ctx: GuildContext, *, obj: DigiObject): userdata = load_or_fake(ctx.author) - await ctx.send(f"{obj.article.capitalize()} {obj.name} scaled for {userdata.nickname} is {obj.get_stats_sentence(userdata.scale, userdata.unitsystem)}") + await ctx.send(f"{obj.article.capitalize()} {obj.name} scaled for {userdata.nickname} is {get_stats_sentence(obj, userdata.scale, userdata.unitsystem)}") async def setup(bot: commands.Bot): diff --git a/sizebot/cogs/pokemon.py b/sizebot/cogs/pokemon.py index c5b9ab9c..378da46b 100644 --- a/sizebot/cogs/pokemon.py +++ b/sizebot/cogs/pokemon.py @@ -5,7 +5,7 @@ from sizebot.lib import userdb from sizebot.lib.pokemon import pokemon -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.userdb import MemberOrFakeOrSize logger = logging.getLogger("sizebot") @@ -38,7 +38,8 @@ async def pokedex(self, ctx: BotContext, pkmn: int | str = None): aliases = ["pokecompare", "pokecomp", "lookatpoke"], category = "objects" ) - async def lookatpokemon(self, ctx: BotContext, pkmn: int | str = None, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def lookatpokemon(self, ctx: GuildContext, pkmn: int | str = None, *, who: MemberOrFakeOrSize = None): """Pokemaaaaaaaaans""" if who is None: who = ctx.author diff --git a/sizebot/cogs/profile.py b/sizebot/cogs/profile.py index 5c48e4e3..c7dc739e 100644 --- a/sizebot/cogs/profile.py +++ b/sizebot/cogs/profile.py @@ -5,7 +5,7 @@ from sizebot.lib import userdb from sizebot.lib.errors import InvalidSizeValue -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.utils import is_url @@ -27,7 +27,7 @@ def __init__(self, bot: commands.Bot): category = "profile" ) @commands.guild_only() - async def setpicture(self, ctx: BotContext, *, url: Annotated[str, parse_url]): + async def setpicture(self, ctx: GuildContext, *, url: Annotated[str, parse_url]): """ Set your profile's image. Must be a valid image URL.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.picture_url = url @@ -41,7 +41,7 @@ async def setpicture(self, ctx: BotContext, *, url: Annotated[str, parse_url]): multiline = True ) @commands.guild_only() - async def setdescription(self, ctx: BotContext, *, desc: str): + async def setdescription(self, ctx: GuildContext, *, desc: str): """Set your profile description. Accepts slightly more markdown than usual, see https://leovoel.github.io/embed-visualizer/""" @@ -55,7 +55,7 @@ async def setdescription(self, ctx: BotContext, *, desc: str): category = "profile" ) @commands.guild_only() - async def resetpicture(self, ctx: BotContext): + async def resetpicture(self, ctx: GuildContext): """Reset your profile's image.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.picture_url = None @@ -67,7 +67,7 @@ async def resetpicture(self, ctx: BotContext): category = "profile" ) @commands.guild_only() - async def resetdescription(self, ctx: BotContext): + async def resetdescription(self, ctx: GuildContext): """Remove your profile description.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.description = None @@ -80,7 +80,7 @@ async def resetdescription(self, ctx: BotContext): category = "profile" ) @commands.guild_only() - async def profile(self, ctx: BotContext, member: discord.Member = None): + async def profile(self, ctx: GuildContext, member: discord.Member = None): """See the profile of you or another SizeBot user. #ALPHA# diff --git a/sizebot/cogs/quake.py b/sizebot/cogs/quake.py index 601dfede..d0ed912d 100644 --- a/sizebot/cogs/quake.py +++ b/sizebot/cogs/quake.py @@ -9,7 +9,7 @@ from sizebot.lib.constants import colors, emojis from sizebot.lib.quake import breath_joules, heartbeat_joules, joules_to_mag, jump_joules, mag_to_name, mag_to_radius, poke_joules, step_joules, stomp_joules, type_joules from sizebot.lib.stats import StatBox -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV from sizebot.lib.userdb import load_or_fake, MemberOrFakeOrSize from sizebot.lib.errors import UserMessedUpException @@ -106,7 +106,8 @@ def __init__(self, bot: commands.Bot): aliases = ["quake"], usage = "[type] [user/height]", category = "stats") - async def earthquake(self, ctx: BotContext, quake_type: QuakeType | None = "step", user: MemberOrFakeOrSize = None): + @commands.guild_only() + async def earthquake(self, ctx: GuildContext, quake_type: QuakeType | None = "step", user: MemberOrFakeOrSize = None): """See what quakes would be caused by your steps.\n#ACC#""" if user is None: user = ctx.author @@ -118,7 +119,8 @@ async def earthquake(self, ctx: BotContext, quake_type: QuakeType | None = "step aliases = ["quakestats"], usage = "[user/height]", category = "stats") - async def earthquakestats(self, ctx: BotContext, user: MemberOrFakeOrSize = None): + @commands.guild_only() + async def earthquakestats(self, ctx: GuildContext, user: MemberOrFakeOrSize = None): """See what quakes would be caused by your steps.\n#ACC#""" if user is None: user = ctx.author @@ -130,7 +132,8 @@ async def earthquakestats(self, ctx: BotContext, user: MemberOrFakeOrSize = None aliases = ["quakecomp"], usage = "[user] [type]", category = "stats") - async def quakecompare(self, ctx: BotContext, user: MemberOrFakeOrSize, quake_type: QuakeType | None = "step"): + @commands.guild_only() + async def quakecompare(self, ctx: GuildContext, user: MemberOrFakeOrSize, quake_type: QuakeType | None = "step"): """See what quakes would be caused by someone else's steps.\n#ACC#""" self_user = load_or_fake(ctx.author) userdata = load_or_fake(user) @@ -144,7 +147,8 @@ async def quakecompare(self, ctx: BotContext, user: MemberOrFakeOrSize, quake_ty aliases = [], usage = " [user/height]", category = "stats") - async def quakewalk(self, ctx: BotContext, dist: SV, user: MemberOrFakeOrSize = None): + @commands.guild_only() + async def quakewalk(self, ctx: GuildContext, dist: SV, user: MemberOrFakeOrSize = None): """Walk a distance and cause some quakes.\n#ACC#""" if user is None: user = ctx.author @@ -176,7 +180,8 @@ async def quakewalk(self, ctx: BotContext, dist: SV, user: MemberOrFakeOrSize = aliases = [], usage = "", category = "stats") - async def quaketype(self, ctx: BotContext, *, s: str): + @commands.guild_only() + async def quaketype(self, ctx: GuildContext, *, s: str): """Type a sentence and cause some quakes.\n#ACC#""" guildid = ctx.guild.id userid = ctx.author.id diff --git a/sizebot/cogs/register.py b/sizebot/cogs/register.py index a02180bd..02e31ddb 100644 --- a/sizebot/cogs/register.py +++ b/sizebot/cogs/register.py @@ -8,7 +8,7 @@ from sizebot.conf import conf from sizebot.lib import errors, userdb, nickmanager from sizebot.lib.constants import ids, emojis -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext logger = logging.getLogger("sizebot") @@ -64,7 +64,7 @@ def __init__(self, bot: commands.Bot): category = "setup" ) @commands.guild_only() - async def register(self, ctx: BotContext): + async def register(self, ctx: GuildContext): # nick: str # currentheight: SV = proportions.defaultheight # baseheight: SV = proportions.defaultheight @@ -149,7 +149,7 @@ def check(reaction: discord.Reaction, reacter: discord.Member | discord.User) -> await show_next_step(ctx, userdata) @register.error - async def register_handler(self, ctx: BotContext, error: commands.CommandError): + async def register_handler(self, ctx: GuildContext, error: commands.CommandError): # Check if required argument is missing if isinstance(error, commands.MissingRequiredArgument): await ctx.send( @@ -162,7 +162,7 @@ async def register_handler(self, ctx: BotContext, error: commands.CommandError): category = "setup" ) @commands.guild_only() - async def unregister(self, ctx: BotContext): + async def unregister(self, ctx: GuildContext): """Unregister your SizeBot profile.""" guild = ctx.guild user = ctx.author @@ -216,7 +216,7 @@ def check(reaction: discord.Reaction, reacter: discord.Member | discord.User) -> category = "setup" ) @commands.guild_only() - async def copy(self, ctx: BotContext): + async def copy(self, ctx: GuildContext): """Copy your SizeBot profile from a different guild to this one.""" inputdict = { diff --git a/sizebot/cogs/say.py b/sizebot/cogs/say.py index 7dfd4f4e..0f0fce93 100644 --- a/sizebot/cogs/say.py +++ b/sizebot/cogs/say.py @@ -6,7 +6,7 @@ from sizebot.lib import userdb from sizebot.lib.constants import emojis -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, Decimal from sizebot.lib.errors import UserNotFoundException from sizebot.lib.stats import AVERAGE_HEIGHT @@ -111,58 +111,58 @@ }) giant_letters = str.maketrans({ - 'a': ' :regional_indicator_a:', - 'b': ' :regional_indicator_b:', - 'c': ' :regional_indicator_c:', - 'd': ' :regional_indicator_d:', - 'e': ' :regional_indicator_e:', - 'f': ' :regional_indicator_f:', - 'g': ' :regional_indicator_g:', - 'h': ' :regional_indicator_h:', - 'i': ' :regional_indicator_i:', - 'j': ' :regional_indicator_j:', - 'k': ' :regional_indicator_k:', - 'l': ' :regional_indicator_l:', - 'm': ' :regional_indicator_m:', - 'n': ' :regional_indicator_n:', - 'o': ' :regional_indicator_o:', - 'p': ' :regional_indicator_p:', - 'q': ' :regional_indicator_q:', - 'r': ' :regional_indicator_r:', - 's': ' :regional_indicator_s:', - 't': ' :regional_indicator_t:', - 'u': ' :regional_indicator_u:', - 'v': ' :regional_indicator_v:', - 'w': ' :regional_indicator_w:', - 'x': ' :regional_indicator_x:', - 'y': ' :regional_indicator_y:', - 'z': ' :regional_indicator_z:', - 'A': ' :regional_indicator_a:', - 'B': ' :regional_indicator_b:', - 'C': ' :regional_indicator_c:', - 'D': ' :regional_indicator_d:', - 'E': ' :regional_indicator_e:', - 'F': ' :regional_indicator_f:', - 'G': ' :regional_indicator_g:', - 'H': ' :regional_indicator_h:', - 'I': ' :regional_indicator_i:', - 'J': ' :regional_indicator_j:', - 'K': ' :regional_indicator_k:', - 'L': ' :regional_indicator_l:', - 'M': ' :regional_indicator_m:', - 'N': ' :regional_indicator_n:', - 'O': ' :regional_indicator_o:', - 'P': ' :regional_indicator_p:', - 'Q': ' :regional_indicator_q:', - 'R': ' :regional_indicator_r:', - 'S': ' :regional_indicator_s:', - 'T': ' :regional_indicator_t:', - 'U': ' :regional_indicator_u:', - 'V': ' :regional_indicator_v:', - 'W': ' :regional_indicator_w:', - 'X': ' :regional_indicator_x:', - 'Y': ' :regional_indicator_y:', - 'Z': ' :regional_indicator_z:', + 'a': ':regional_indicator_a:', + 'b': ':regional_indicator_b:', + 'c': ':regional_indicator_c:', + 'd': ':regional_indicator_d:', + 'e': ':regional_indicator_e:', + 'f': ':regional_indicator_f:', + 'g': ':regional_indicator_g:', + 'h': ':regional_indicator_h:', + 'i': ':regional_indicator_i:', + 'j': ':regional_indicator_j:', + 'k': ':regional_indicator_k:', + 'l': ':regional_indicator_l:', + 'm': ':regional_indicator_m:', + 'n': ':regional_indicator_n:', + 'o': ':regional_indicator_o:', + 'p': ':regional_indicator_p:', + 'q': ':regional_indicator_q:', + 'r': ':regional_indicator_r:', + 's': ':regional_indicator_s:', + 't': ':regional_indicator_t:', + 'u': ':regional_indicator_u:', + 'v': ':regional_indicator_v:', + 'w': ':regional_indicator_w:', + 'x': ':regional_indicator_x:', + 'y': ':regional_indicator_y:', + 'z': ':regional_indicator_z:', + 'A': ':regional_indicator_a:', + 'B': ':regional_indicator_b:', + 'C': ':regional_indicator_c:', + 'D': ':regional_indicator_d:', + 'E': ':regional_indicator_e:', + 'F': ':regional_indicator_f:', + 'G': ':regional_indicator_g:', + 'H': ':regional_indicator_h:', + 'I': ':regional_indicator_i:', + 'J': ':regional_indicator_j:', + 'K': ':regional_indicator_k:', + 'L': ':regional_indicator_l:', + 'M': ':regional_indicator_m:', + 'N': ':regional_indicator_n:', + 'O': ':regional_indicator_o:', + 'P': ':regional_indicator_p:', + 'Q': ':regional_indicator_q:', + 'R': ':regional_indicator_r:', + 'S': ':regional_indicator_s:', + 'T': ':regional_indicator_t:', + 'U': ':regional_indicator_u:', + 'V': ':regional_indicator_v:', + 'W': ':regional_indicator_w:', + 'X': ':regional_indicator_x:', + 'Y': ':regional_indicator_y:', + 'Z': ':regional_indicator_z:', '0': ':zero:', '1': ':one:', '2': ':two:', @@ -267,7 +267,8 @@ def __init__(self, bot: commands.Bot): category = "fun", multiline = True ) - async def say(self, ctx: BotContext, *, message: str): + @commands.guild_only() + async def say(self, ctx: GuildContext, *, message: str): """Talk to the world!""" # PERMISSION: requires manage_messages await ctx.message.delete(delay=0) @@ -292,7 +293,8 @@ async def say(self, ctx: BotContext, *, message: str): category = "fun", multiline = True ) - async def sayto(self, ctx: BotContext, memberOrHeight: discord.Member | SV, *, message: str): + @commands.guild_only() + async def sayto(self, ctx: GuildContext, memberOrHeight: discord.Member | SV, *, message: str): """Talk to someone!""" # PERMISSION: requires manage_messages await ctx.message.delete(delay=0) diff --git a/sizebot/cogs/scaletalk.py b/sizebot/cogs/scaletalk.py index d1908d4b..8cec6f92 100644 --- a/sizebot/cogs/scaletalk.py +++ b/sizebot/cogs/scaletalk.py @@ -1,6 +1,7 @@ import logging import re from copy import copy +from typing import cast import discord from discord.ext import commands @@ -8,7 +9,7 @@ from sizebot.lib import userdb from sizebot.lib.diff import Diff from sizebot.lib.errors import ChangeMethodInvalidException, UserMessedUpException, UserNotFoundException, ValueIsZeroException -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, Decimal from sizebot.lib.utils import try_int @@ -27,7 +28,8 @@ def __init__(self, bot: commands.Bot): category = "scalestep", usage = "" ) - async def settalkscale(self, ctx: BotContext, *, change: str): + @commands.guild_only() + async def settalkscale(self, ctx: GuildContext, *, change: str): """Set the amount you scale per character. Sets the amount that you scale for each character you type. @@ -37,6 +39,8 @@ async def settalkscale(self, ctx: BotContext, *, change: str): `&settalkscale 2x/100` `&settalkscale -1mm` (defaults to per 1 character) """ + if ctx.guild is None: + raise commands.errors.NoPrivateMessage() guildid = ctx.guild.id userid = ctx.author.id @@ -84,9 +88,9 @@ async def settalkscale(self, ctx: BotContext, *, change: str): "cleartypescale", "unsettypescale", "resetscaletype", "clearscaletype", "unsetscaletype", "resettypescale"] ) - async def resettalkscale(self, ctx: BotContext): + @commands.guild_only() + async def resettalkscale(self, ctx: GuildContext): """Clear your talk-scale amount.""" - guildid = ctx.guild.id userid = ctx.author.id @@ -119,9 +123,9 @@ async def on_message(self, message: discord.Message): return if userdata.currentscaletalk.changetype == "add": - userdata.height += (userdata.currentscaletalk.amount * length) + userdata.height += (cast(SV, userdata.currentscaletalk.amount) * length) elif userdata.currentscaletalk.changetype == "multiply": - userdata.height *= (userdata.currentscaletalk.amount ** length) + userdata.height *= (cast(Decimal, userdata.currentscaletalk.amount) ** length) userdb.save(userdata) diff --git a/sizebot/cogs/scalewalk.py b/sizebot/cogs/scalewalk.py index a630fb9f..b6a38160 100644 --- a/sizebot/cogs/scalewalk.py +++ b/sizebot/cogs/scalewalk.py @@ -10,7 +10,7 @@ from sizebot.lib.errors import ChangeMethodInvalidException, DigiContextException, ValueIsZeroException from sizebot.lib.loglevels import EGG from sizebot.lib.stats import StatBox -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, Decimal from sizebot.lib.utils import try_int @@ -96,7 +96,8 @@ def __init__(self, bot: commands.Bot): category = "scalestep", usage = " [apply]" ) - async def scalewalk(self, ctx: BotContext, change: Diff, dist: SV, flag: str | None = None): + @commands.guild_only() + async def scalewalk(self, ctx: GuildContext, change: Diff, dist: SV, flag: str | None = None): """Walk a certain distance, scaling by an amount each step you take. Accepts addition or subtraction of a certain height, or multiplication/division of a factor. @@ -148,7 +149,8 @@ async def scalewalk(self, ctx: BotContext, change: Diff, dist: SV, flag: str | N category = "scalestep", usage = " [apply]" ) - async def scalerun(self, ctx: BotContext, change: Diff, dist: SV, flag: str | None = None): + @commands.guild_only() + async def scalerun(self, ctx: GuildContext, change: Diff, dist: SV, flag: str | None = None): """Run a certain distance, scaling by an amount each step you take. Accepts addition or subtraction of a certain height, or multiplication/division of a factor. @@ -201,7 +203,8 @@ async def scalerun(self, ctx: BotContext, change: Diff, dist: SV, flag: str | No category = "scalestep", usage = "" ) - async def setstepscale(self, ctx: BotContext, *, change: Diff): + @commands.guild_only() + async def setstepscale(self, ctx: GuildContext, *, change: Diff): """Set the amount you scale per step, for use with `&step`. Sets the amount that you scale for each `&step` you take. @@ -228,7 +231,8 @@ async def setstepscale(self, ctx: BotContext, *, change: Diff): "clearwalkscale", "unsetwalkscale", "resetscalewalk", "clearscalewalk", "unsetscalewalk", "resetwalkscale"] ) - async def resetstepscale(self, ctx: BotContext): + @commands.guild_only() + async def resetstepscale(self, ctx: GuildContext): """Clear your step-scale amount, for use with `&step`.""" guildid = ctx.guild.id @@ -242,7 +246,8 @@ async def resetstepscale(self, ctx: BotContext): @commands.command( category = "scalestep", ) - async def step(self, ctx: BotContext, steps: int | None = None): + @commands.guild_only() + async def step(self, ctx: GuildContext, steps: int | None = None): """Step a certain number of times, scaling by the amount set in `&setscalestep`. Scales you the amount that you would change depending on the scale factor you diff --git a/sizebot/cogs/set.py b/sizebot/cogs/set.py index 0d032d47..5355c5a9 100644 --- a/sizebot/cogs/set.py +++ b/sizebot/cogs/set.py @@ -11,7 +11,7 @@ from sizebot.lib.loglevels import EGG from sizebot.lib.shoesize import to_shoe_size, from_shoe_size from sizebot.lib.stats import HOUR -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, WV, pos_SV, pos_WV, Decimal from sizebot.lib.unitsystem import UnitSystem, parse_unitsystem from sizebot.lib.utils import parse_scale, randrange_log @@ -29,7 +29,7 @@ def __init__(self, bot: commands.Bot): category = "set" ) @commands.guild_only() - async def setnick(self, ctx: BotContext, *, newnick: str): + async def setnick(self, ctx: GuildContext, *, newnick: str): """Change nickname.""" # TODO: Disable and hide this command on servers where bot does not have MANAGE_NICKNAMES permission # TODO: If the bot has MANAGE_NICKNAMES permission but can't change this user's permission, let the user know @@ -48,7 +48,7 @@ async def setnick(self, ctx: BotContext, *, newnick: str): category = "set" ) @commands.guild_only() - async def setspecies(self, ctx: BotContext, *, newspecies: str): + async def setspecies(self, ctx: GuildContext, *, newspecies: str): """Change species.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.species = newspecies @@ -64,7 +64,7 @@ async def setspecies(self, ctx: BotContext, *, newspecies: str): category = "set" ) @commands.guild_only() - async def resetspecies(self, ctx: BotContext): + async def resetspecies(self, ctx: GuildContext): """Remove species.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.species = None @@ -80,7 +80,7 @@ async def resetspecies(self, ctx: BotContext): category = "set" ) @commands.guild_only() - async def setdisplay(self, ctx: BotContext, newdisp: bool): + async def setdisplay(self, ctx: GuildContext, newdisp: bool): """Set display mode.""" # TODO: Disable and hide this command on servers where bot does not have MANAGE_NICKNAMES permission # TODO: If the bot has MANAGE_NICKNAMES permission but can't change this user's permission, let the user know @@ -103,7 +103,7 @@ async def setdisplay(self, ctx: BotContext, newdisp: bool): category = "set" ) @commands.guild_only() - async def setsystem(self, ctx: BotContext, newsys: Annotated[UnitSystem, parse_unitsystem]): + async def setsystem(self, ctx: GuildContext, newsys: Annotated[UnitSystem, parse_unitsystem]): """Set measurement system. (M or U.)""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.unitsystem = newsys @@ -121,7 +121,7 @@ async def setsystem(self, ctx: BotContext, newsys: Annotated[UnitSystem, parse_u category = "set" ) @commands.guild_only() - async def setheight(self, ctx: BotContext, *, newheight: SV): + async def setheight(self, ctx: GuildContext, *, newheight: SV): """Change height.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.height = newheight @@ -138,7 +138,7 @@ async def setheight(self, ctx: BotContext, *, newheight: SV): category = "set" ) @commands.guild_only() - async def setscale(self, ctx: BotContext, *, newscale: Annotated[Decimal, parse_scale] | Literal["banana"]): + async def setscale(self, ctx: GuildContext, *, newscale: Annotated[Decimal, parse_scale] | Literal["banana"]): """Change height by scale.""" if newscale == "banana": @@ -161,7 +161,7 @@ async def setscale(self, ctx: BotContext, *, newscale: Annotated[Decimal, parse_ category = "set" ) @commands.guild_only() - async def setso(self, ctx: BotContext, sv1: discord.Member | SV, sv2: SV): + async def setso(self, ctx: GuildContext, sv1: discord.Member | SV, sv2: SV): """Change height by scale.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) if isinstance(sv1, discord.Member): @@ -180,7 +180,7 @@ async def setso(self, ctx: BotContext, sv1: discord.Member | SV, sv2: SV): category = "set" ) @commands.guild_only() - async def copyheight(self, ctx: BotContext, from_user: discord.Member, *, newscale: Annotated[Decimal, parse_scale] = Decimal(1)): + async def copyheight(self, ctx: GuildContext, from_user: discord.Member, *, newscale: Annotated[Decimal, parse_scale] = Decimal(1)): """Be the size of another user, modified by a factor. Examples: @@ -203,7 +203,7 @@ async def copyheight(self, ctx: BotContext, from_user: discord.Member, *, newsca category = "set" ) @commands.guild_only() - async def resetheight(self, ctx: BotContext): + async def resetheight(self, ctx: GuildContext): """Reset height/size.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.height = userdata.baseheight @@ -220,7 +220,7 @@ async def resetheight(self, ctx: BotContext): category = "set" ) @commands.guild_only() - async def setrandomheight(self, ctx: BotContext, minheight: SV, maxheight: SV): + async def setrandomheight(self, ctx: GuildContext, minheight: SV, maxheight: SV): """Change height to a random value. Sets your height to a height between `minheight` and `maxheight`. @@ -248,7 +248,7 @@ async def setrandomheight(self, ctx: BotContext, minheight: SV, maxheight: SV): category = "set" ) @commands.guild_only() - async def setrandomscale(self, ctx: BotContext, minscale: Annotated[Decimal, parse_scale], maxscale: Annotated[Decimal, parse_scale]): + async def setrandomscale(self, ctx: GuildContext, minscale: Annotated[Decimal, parse_scale], maxscale: Annotated[Decimal, parse_scale]): """Change scale to a random value.""" if minscale < 0: minscale = Decimal(0) @@ -272,7 +272,7 @@ async def setrandomscale(self, ctx: BotContext, minscale: Annotated[Decimal, par category = "set" ) @commands.guild_only() - async def setinf(self, ctx: BotContext): + async def setinf(self, ctx: GuildContext): """Change height to infinity.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.height = SV("infinity") @@ -289,7 +289,7 @@ async def setinf(self, ctx: BotContext): category = "set" ) @commands.guild_only() - async def set0(self, ctx: BotContext): + async def set0(self, ctx: GuildContext): """Change height to a zero.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.height = 0 @@ -305,7 +305,8 @@ async def set0(self, ctx: BotContext): usage = "", category = "set" ) - async def setweight(self, ctx: BotContext, *, newweight: WV): + @commands.guild_only() + async def setweight(self, ctx: GuildContext, *, newweight: WV): """Set your current weight.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currweight = newweight @@ -323,7 +324,8 @@ async def setweight(self, ctx: BotContext, *, newweight: WV): usage = "", category = "set" ) - async def setfoot(self, ctx: BotContext, *, newfoot: Annotated[SV, pos_SV]): + @commands.guild_only() + async def setfoot(self, ctx: GuildContext, *, newfoot: Annotated[SV, pos_SV]): """Set your current foot length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currfoot = newfoot @@ -340,7 +342,8 @@ async def setfoot(self, ctx: BotContext, *, newfoot: Annotated[SV, pos_SV]): usage = "", category = "set" ) - async def setshoe(self, ctx: BotContext, *, newshoe: str): + @commands.guild_only() + async def setshoe(self, ctx: GuildContext, *, newshoe: str): """Set your current shoe size. Accepts a US Shoe Size. @@ -367,7 +370,7 @@ async def setshoe(self, ctx: BotContext, *, newshoe: str): category = "set" ) @commands.guild_only() - async def resetfoot(self, ctx: BotContext): + async def resetfoot(self, ctx: GuildContext): """Remove custom foot length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.footlength = None @@ -381,7 +384,7 @@ async def resetfoot(self, ctx: BotContext): category = "set" ) @commands.guild_only() - async def togglepaw(self, ctx: BotContext): + async def togglepaw(self, ctx: GuildContext): """Switch between the word "foot" and "paw" for your stats.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.pawtoggle = not userdata.pawtoggle @@ -395,7 +398,7 @@ async def togglepaw(self, ctx: BotContext): category = "set" ) @commands.guild_only() - async def togglefur(self, ctx: BotContext): + async def togglefur(self, ctx: GuildContext): """Switch between the word "hair" and "fur" for your stats.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.furtoggle = not userdata.furtoggle @@ -408,7 +411,8 @@ async def togglefur(self, ctx: BotContext): usage = "", category = "set" ) - async def sethair(self, ctx: BotContext, *, newhair: Annotated[SV, pos_SV]): + @commands.guild_only() + async def sethair(self, ctx: GuildContext, *, newhair: Annotated[SV, pos_SV]): """Set your current hair length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.hairlength = SV(newhair / userdata.scale) @@ -422,7 +426,8 @@ async def sethair(self, ctx: BotContext, *, newhair: Annotated[SV, pos_SV]): usage = "", category = "set" ) - async def settail(self, ctx: BotContext, *, newtail: Annotated[SV, pos_SV]): + @commands.guild_only() + async def settail(self, ctx: GuildContext, *, newtail: Annotated[SV, pos_SV]): """Set your current tail length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.taillength = SV(newtail / userdata.scale) @@ -437,7 +442,7 @@ async def settail(self, ctx: BotContext, *, newtail: Annotated[SV, pos_SV]): category = "set" ) @commands.guild_only() - async def resettail(self, ctx: BotContext): + async def resettail(self, ctx: GuildContext): """Remove custom tail length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.taillength = None @@ -450,7 +455,8 @@ async def resettail(self, ctx: BotContext): usage = "", category = "set" ) - async def setear(self, ctx: BotContext, *, newear: Annotated[SV, pos_SV]): + @commands.guild_only() + async def setear(self, ctx: GuildContext, *, newear: Annotated[SV, pos_SV]): """Set your current ear heightear.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currear = newear @@ -467,7 +473,7 @@ async def setear(self, ctx: BotContext, *, newear: Annotated[SV, pos_SV]): category = "set" ) @commands.guild_only() - async def resetear(self, ctx: BotContext): + async def resetear(self, ctx: GuildContext): """Remove custom ear height.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.earheight = None @@ -481,7 +487,8 @@ async def resetear(self, ctx: BotContext): usage = "", category = "set" ) - async def setstrength(self, ctx: BotContext, *, newstrength: Annotated[WV, pos_WV]): + @commands.guild_only() + async def setstrength(self, ctx: GuildContext, *, newstrength: Annotated[WV, pos_WV]): """Set your current lift/carry strength.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currstrength = newstrength @@ -498,7 +505,7 @@ async def setstrength(self, ctx: BotContext, *, newstrength: Annotated[WV, pos_W category = "set" ) @commands.guild_only() - async def resetstrength(self, ctx: BotContext): + async def resetstrength(self, ctx: GuildContext): """Remove custom lift/carry strength.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.liftstrength = None @@ -511,7 +518,8 @@ async def resetstrength(self, ctx: BotContext): usage = "", category = "set" ) - async def setwalk(self, ctx: BotContext, *, newspeed: LinearRate): + @commands.guild_only() + async def setwalk(self, ctx: GuildContext, *, newspeed: LinearRate): """Set your current walk speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currspeed = SV(newspeed.addPerSec * HOUR) @@ -527,7 +535,7 @@ async def setwalk(self, ctx: BotContext, *, newspeed: LinearRate): category = "set" ) @commands.guild_only() - async def resetwalk(self, ctx: BotContext): + async def resetwalk(self, ctx: GuildContext): """Remove custom walk speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.walkperhour = None @@ -540,7 +548,8 @@ async def resetwalk(self, ctx: BotContext): usage = "", category = "set" ) - async def setrun(self, ctx: BotContext, *, newspeed: LinearRate): + @commands.guild_only() + async def setrun(self, ctx: GuildContext, *, newspeed: LinearRate): """Set your current run speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currspeed = SV(newspeed.addPerSec * HOUR) @@ -556,7 +565,7 @@ async def setrun(self, ctx: BotContext, *, newspeed: LinearRate): category = "set" ) @commands.guild_only() - async def resetrun(self, ctx: BotContext): + async def resetrun(self, ctx: GuildContext): """Remove custom run speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.runperhour = None @@ -569,7 +578,8 @@ async def resetrun(self, ctx: BotContext): usage = "", category = "set" ) - async def setswim(self, ctx: BotContext, *, newspeed: LinearRate): + @commands.guild_only() + async def setswim(self, ctx: GuildContext, *, newspeed: LinearRate): """Set your current swim speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) currspeed = SV(newspeed.addPerSec * HOUR) @@ -585,7 +595,7 @@ async def setswim(self, ctx: BotContext, *, newspeed: LinearRate): category = "set" ) @commands.guild_only() - async def resetswim(self, ctx: BotContext): + async def resetswim(self, ctx: GuildContext): """Remove custom swim speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.swimperhour = None @@ -599,7 +609,7 @@ async def resetswim(self, ctx: BotContext): category = "set" ) @commands.guild_only() - async def setgender(self, ctx: BotContext, gender: Annotated[Gender, parse_gender]): + async def setgender(self, ctx: GuildContext, gender: Annotated[Gender, parse_gender]): userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.gender = gender userdb.save(userdata) @@ -614,7 +624,7 @@ async def setgender(self, ctx: BotContext, gender: Annotated[Gender, parse_gende category = "set" ) @commands.guild_only() - async def resetgender(self, ctx: BotContext): + async def resetgender(self, ctx: GuildContext): """Reset gender.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.gender = None @@ -632,7 +642,7 @@ async def resetgender(self, ctx: BotContext): ) @commands.guild_only() @commands.is_owner() - async def setmodel(self, ctx: BotContext, user: discord.Member, *, model: str): + async def setmodel(self, ctx: GuildContext, user: discord.Member, *, model: str): userdata = userdb.load(ctx.guild.id, user.id) userdata.macrovision_model = model userdb.save(userdata) @@ -646,7 +656,7 @@ async def setmodel(self, ctx: BotContext, user: discord.Member, *, model: str): ) @commands.guild_only() @commands.is_owner() - async def clearmodel(self, ctx: BotContext, *, user: discord.Member): + async def clearmodel(self, ctx: GuildContext, *, user: discord.Member): userdata = userdb.load(ctx.guild.id, user.id) userdata.macrovision_model = None userdb.save(userdata) @@ -659,7 +669,7 @@ async def clearmodel(self, ctx: BotContext, *, user: discord.Member): ) @commands.guild_only() @commands.is_owner() - async def setview(self, ctx: BotContext, user: discord.Member, *, view: str): + async def setview(self, ctx: GuildContext, user: discord.Member, *, view: str): userdata = userdb.load(ctx.guild.id, user.id) userdata.macrovision_view = view userdb.save(userdata) @@ -673,7 +683,7 @@ async def setview(self, ctx: BotContext, user: discord.Member, *, view: str): ) @commands.guild_only() @commands.is_owner() - async def clearview(self, ctx: BotContext, *, user: discord.Member): + async def clearview(self, ctx: GuildContext, *, user: discord.Member): userdata = userdb.load(ctx.guild.id, user.id) userdata.macrovision_view = None userdb.save(userdata) diff --git a/sizebot/cogs/setbase.py b/sizebot/cogs/setbase.py index 4d14d5b3..c2910dc3 100644 --- a/sizebot/cogs/setbase.py +++ b/sizebot/cogs/setbase.py @@ -9,7 +9,7 @@ from sizebot.lib.diff import LinearRate from sizebot.lib.shoesize import to_shoe_size, from_shoe_size from sizebot.lib.stats import HOUR -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, WV, pos_SV, pos_WV logger = logging.getLogger("sizebot") @@ -24,7 +24,7 @@ def __init__(self, bot: commands.Bot): category = "setbase" ) @commands.guild_only() - async def setbaseheight(self, ctx: BotContext, *, newbaseheight: Annotated[SV, pos_SV]): + async def setbaseheight(self, ctx: GuildContext, *, newbaseheight: Annotated[SV, pos_SV]): """Change base height.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) @@ -47,7 +47,7 @@ async def setbaseheight(self, ctx: BotContext, *, newbaseheight: Annotated[SV, p category = "setbase" ) @commands.guild_only() - async def setbaseweight(self, ctx: BotContext, *, newweight: Annotated[WV, pos_WV]): + async def setbaseweight(self, ctx: GuildContext, *, newweight: Annotated[WV, pos_WV]): """Change base weight.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) @@ -69,7 +69,7 @@ async def setbaseweight(self, ctx: BotContext, *, newweight: Annotated[WV, pos_W category = "setbase" ) @commands.guild_only() - async def setbase(self, ctx: BotContext, arg1: Annotated[SV, pos_SV] | Annotated[WV, pos_WV], arg2: Annotated[SV, pos_SV] | Annotated[WV, pos_WV] = None): + async def setbase(self, ctx: GuildContext, arg1: Annotated[SV, pos_SV] | Annotated[WV, pos_WV], arg2: Annotated[SV, pos_SV] | Annotated[WV, pos_WV] = None): """Set your base height and weight.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) @@ -108,7 +108,7 @@ async def setbase(self, ctx: BotContext, arg1: Annotated[SV, pos_SV] | Annotated category = "setbase" ) @commands.guild_only() - async def setbasefoot(self, ctx: BotContext, *, newfoot: Annotated[SV, pos_SV]): + async def setbasefoot(self, ctx: GuildContext, *, newfoot: Annotated[SV, pos_SV]): """Set a custom foot length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) basefoot = newfoot @@ -124,7 +124,7 @@ async def setbasefoot(self, ctx: BotContext, *, newfoot: Annotated[SV, pos_SV]): category = "setbase" ) @commands.guild_only() - async def setbaseshoe(self, ctx: BotContext, *, newshoe: str): + async def setbaseshoe(self, ctx: GuildContext, *, newshoe: str): """Set a custom base shoe size. Accepts a US Shoe Size. @@ -149,7 +149,7 @@ async def setbaseshoe(self, ctx: BotContext, *, newshoe: str): category = "setbase" ) @commands.guild_only() - async def setbasehair(self, ctx: BotContext, *, newhair: Annotated[SV, pos_SV]): + async def setbasehair(self, ctx: GuildContext, *, newhair: Annotated[SV, pos_SV]): """Set a custom base hair length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.hairlength = newhair @@ -163,7 +163,7 @@ async def setbasehair(self, ctx: BotContext, *, newhair: Annotated[SV, pos_SV]): category = "set" ) @commands.guild_only() - async def resethair(self, ctx: BotContext): + async def resethair(self, ctx: GuildContext): """Remove custom hair length.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) @@ -178,7 +178,7 @@ async def resethair(self, ctx: BotContext): category = "setbase" ) @commands.guild_only() - async def setbasetail(self, ctx: BotContext, *, newtail: str): + async def setbasetail(self, ctx: GuildContext, *, newtail: str): """Set a custom tail length.""" newtailsv = SV.parse(newtail) @@ -195,7 +195,7 @@ async def setbasetail(self, ctx: BotContext, *, newtail: str): category = "setbase" ) @commands.guild_only() - async def setbaseear(self, ctx: BotContext, *, newear: Annotated[SV, pos_SV]): + async def setbaseear(self, ctx: GuildContext, *, newear: Annotated[SV, pos_SV]): """Set a custom ear height.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.earheight = newear @@ -210,7 +210,7 @@ async def setbaseear(self, ctx: BotContext, *, newear: Annotated[SV, pos_SV]): category = "setbase" ) @commands.guild_only() - async def setbasestrength(self, ctx: BotContext, *, newstrength: Annotated[WV, pos_WV]): + async def setbasestrength(self, ctx: GuildContext, *, newstrength: Annotated[WV, pos_WV]): """Set a custom lift/carry strength.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) @@ -226,7 +226,7 @@ async def setbasestrength(self, ctx: BotContext, *, newstrength: Annotated[WV, p category = "setbase" ) @commands.guild_only() - async def setbasewalk(self, ctx: BotContext, *, newwalk: LinearRate): + async def setbasewalk(self, ctx: GuildContext, *, newwalk: LinearRate): """Set a custom walk speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.walkperhour = SV(newwalk.addPerSec * HOUR) @@ -240,7 +240,7 @@ async def setbasewalk(self, ctx: BotContext, *, newwalk: LinearRate): category = "setbase" ) @commands.guild_only() - async def setbaserun(self, ctx: BotContext, *, newrun: LinearRate): + async def setbaserun(self, ctx: GuildContext, *, newrun: LinearRate): """Set a custom run speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) userdata.runperhour = SV(newrun.addPerSec * HOUR) @@ -254,7 +254,7 @@ async def setbaserun(self, ctx: BotContext, *, newrun: LinearRate): category = "setbase" ) @commands.guild_only() - async def setbaseswim(self, ctx: BotContext, *, newswim: LinearRate): + async def setbaseswim(self, ctx: GuildContext, *, newswim: LinearRate): """Set a custom swim speed.""" userdata = userdb.load(ctx.guild.id, ctx.author.id, allow_unreg=True) diff --git a/sizebot/cogs/stats.py b/sizebot/cogs/stats.py index 2be47f65..089bcc56 100644 --- a/sizebot/cogs/stats.py +++ b/sizebot/cogs/stats.py @@ -12,7 +12,7 @@ from sizebot.lib.neuron import get_neuron_embed from sizebot.lib.objs import format_close_object_smart from sizebot.lib.statproxy import StatProxy -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext from sizebot.lib.units import SV, TV, WV, Decimal from sizebot.lib.userdb import load_or_fake, MemberOrFakeOrSize, load_or_fake_height, load_or_fake_weight from sizebot.lib.utils import pretty_time_delta, sentence_join, round_fraction @@ -29,7 +29,7 @@ def __init__(self, bot: commands.Bot): category = "stats" ) @commands.guild_only() - async def stats(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize | None = None): + async def stats(self, ctx: GuildContext, memberOrHeight: MemberOrFakeOrSize | None = None): """User stats command. Get tons of user stats about yourself, a user, or a raw height. @@ -55,7 +55,7 @@ async def stats(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize | None category = "stats" ) @commands.guild_only() - async def statsso(self, ctx: BotContext, sv1: MemberOrFakeOrSize, sv2: SV, *, memberOrHeight: MemberOrFakeOrSize = None): + async def statsso(self, ctx: GuildContext, sv1: MemberOrFakeOrSize, sv2: SV, *, memberOrHeight: MemberOrFakeOrSize = None): """Stats so that from looks like to. """ if memberOrHeight is None: @@ -78,7 +78,7 @@ async def statsso(self, ctx: BotContext, sv1: MemberOrFakeOrSize, sv2: SV, *, me category = "stats" ) @commands.guild_only() - async def basestats(self, ctx: BotContext, *, member: discord.Member = None): + async def basestats(self, ctx: GuildContext, *, member: discord.Member = None): """Show user stats at 1x scale. Get the 1x scale stats about yourself or a user. @@ -103,7 +103,7 @@ async def basestats(self, ctx: BotContext, *, member: discord.Member = None): category = "stats" ) @commands.guild_only() - async def settings(self, ctx: BotContext, *, member: discord.Member = None): + async def settings(self, ctx: GuildContext, *, member: discord.Member = None): """Show settable values on SizeBot. Get all settable values on SizeBot for yourself or a user. @@ -128,7 +128,7 @@ async def settings(self, ctx: BotContext, *, member: discord.Member = None): category = "stats" ) @commands.guild_only() - async def statsas(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = None, + async def statsas(self, ctx: GuildContext, memberOrHeight: MemberOrFakeOrSize = None, memberOrHeight2: MemberOrFakeOrSize = None): """User stats command with modified bases. @@ -158,7 +158,7 @@ async def statsas(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = No category = "stats" ) @commands.guild_only() - async def stat(self, ctx: BotContext, stat: StatProxy, *, memberOrHeight: MemberOrFakeOrSize = None): + async def stat(self, ctx: GuildContext, stat: StatProxy, *, memberOrHeight: MemberOrFakeOrSize = None): """User stat command. Get a single stat about yourself, a user, or a raw height. @@ -194,7 +194,7 @@ async def stat(self, ctx: BotContext, stat: StatProxy, *, memberOrHeight: Member category = "stats" ) @commands.guild_only() - async def statso(self, ctx: BotContext, sv1: MemberOrFakeOrSize, sv2: SV, stat: StatProxy, *, memberOrHeight: MemberOrFakeOrSize = None): + async def statso(self, ctx: GuildContext, sv1: MemberOrFakeOrSize, sv2: SV, stat: StatProxy, *, memberOrHeight: MemberOrFakeOrSize = None): """User stat command as if an implied scale. Available stats are: #STATS# @@ -227,7 +227,7 @@ async def statso(self, ctx: BotContext, sv1: MemberOrFakeOrSize, sv2: SV, stat: category = "stats" ) @commands.guild_only() - async def statas(self, ctx: BotContext, stat: StatProxy, memberOrHeight: MemberOrFakeOrSize = None, + async def statas(self, ctx: GuildContext, stat: StatProxy, memberOrHeight: MemberOrFakeOrSize = None, memberOrHeight2: MemberOrFakeOrSize = None): """User stat command with custom bases. @@ -267,7 +267,7 @@ async def statas(self, ctx: BotContext, stat: StatProxy, memberOrHeight: MemberO category = "stats" ) @commands.guild_only() - async def compare(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = None, + async def compare(self, ctx: GuildContext, memberOrHeight: MemberOrFakeOrSize = None, *, memberOrHeight2: MemberOrFakeOrSize = None): """Compare two users' size. @@ -293,7 +293,7 @@ async def compare(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = No category = "stats" ) @commands.guild_only() - async def compareas(self, ctx: BotContext, asHeight: MemberOrFakeOrSize = None, + async def compareas(self, ctx: GuildContext, asHeight: MemberOrFakeOrSize = None, memberOrHeight: MemberOrFakeOrSize = None): """Compare yourself as a different height and another user.""" @@ -314,7 +314,7 @@ async def compareas(self, ctx: BotContext, asHeight: MemberOrFakeOrSize = None, category = "stats" ) @commands.guild_only() - async def comparestat(self, ctx: BotContext, stat: StatProxy, *, memberOrHeight: MemberOrFakeOrSize, memberOrHeight2: MemberOrFakeOrSize = None): + async def comparestat(self, ctx: GuildContext, stat: StatProxy, *, memberOrHeight: MemberOrFakeOrSize, memberOrHeight2: MemberOrFakeOrSize = None): if memberOrHeight2 is None: memberOrHeight2 = ctx.author @@ -341,7 +341,8 @@ async def comparestat(self, ctx: BotContext, stat: StatProxy, *, memberOrHeight: usage = " [user]", category = "stats" ) - async def distance(self, ctx: BotContext, goal: MemberOrFakeOrSize | TV, + @commands.guild_only() + async def distance(self, ctx: GuildContext, goal: MemberOrFakeOrSize | TV, *, member: MemberOrFakeOrSize = None): """How long will it take to walk, run, climb, etc. a distance/time? @@ -371,7 +372,7 @@ async def distance(self, ctx: BotContext, goal: MemberOrFakeOrSize | TV, category = "stats" ) @commands.guild_only() - async def distancestats(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = None, + async def distancestats(self, ctx: GuildContext, memberOrHeight: MemberOrFakeOrSize = None, *, memberOrHeight2: MemberOrFakeOrSize = None): """Find how long it would take to travel across a person.""" if memberOrHeight2 is None: @@ -394,7 +395,7 @@ async def distancestats(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSiz category = "stats" ) @commands.guild_only() - async def distancestat(self, ctx: BotContext, stat: str, memberOrHeight: MemberOrFakeOrSize = None, + async def distancestat(self, ctx: GuildContext, stat: str, memberOrHeight: MemberOrFakeOrSize = None, *, memberOrHeight2: MemberOrFakeOrSize = None): """Find how long it would take to travel across a certain distance on a person. @@ -422,7 +423,8 @@ async def distancestat(self, ctx: BotContext, stat: str, memberOrHeight: MemberO usage = " [user]", category = "stats" ) - async def ruler(self, ctx: BotContext, length: SV, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def ruler(self, ctx: GuildContext, length: SV, *, who: MemberOrFakeOrSize = None): """A distance to a user looks how long to everyone else? Examples: @@ -453,7 +455,8 @@ async def ruler(self, ctx: BotContext, length: SV, *, who: MemberOrFakeOrSize = usage = "", category = "stats" ) - async def sound(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def sound(self, ctx: GuildContext, *, who: MemberOrFakeOrSize = None): """Find how long it would take sound to travel a length or height.""" ONE_SOUNDSECOND = SV(340.27) is_SV = False @@ -485,7 +488,8 @@ async def sound(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): usage = "", category = "stats" ) - async def light(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def light(self, ctx: GuildContext, *, who: MemberOrFakeOrSize = None): """Find how long it would take light to travel a length or height.""" ONE_LIGHTSECOND = SV(299792000) is_SV = False @@ -516,7 +520,8 @@ async def light(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): @commands.command( usage = "" ) - async def fall(self, ctx: BotContext, distance: MemberOrFakeOrSize): + @commands.guild_only() + async def fall(self, ctx: GuildContext, distance: MemberOrFakeOrSize): """ Fall down. #ACC# @@ -537,7 +542,7 @@ async def fall(self, ctx: BotContext, distance: MemberOrFakeOrSize): usage = "" ) @commands.guild_only() - async def mcfall(self, ctx: BotContext, distance: discord.Member | SV): + async def mcfall(self, ctx: GuildContext, distance: discord.Member | SV): if ctx.guild is None: raise commands.errors.NoPrivateMessage() if isinstance(distance, discord.Member): @@ -556,7 +561,7 @@ async def mcfall(self, ctx: BotContext, distance: discord.Member | SV): category = "stats" ) @commands.guild_only() - async def lineup(self, ctx: BotContext): + async def lineup(self, ctx: GuildContext): """Lineup a bunch of people for comparison.""" # TODO: Oh god this sucks, and doesn't support raw heights because of it if not ctx.message.mentions: @@ -599,7 +604,7 @@ async def lineup(self, ctx: BotContext): category = "stats" ) @commands.guild_only() - async def simplecompare(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = None, + async def simplecompare(self, ctx: GuildContext, memberOrHeight: MemberOrFakeOrSize = None, *, memberOrHeight2: MemberOrFakeOrSize = None): """Compare two users' size. @@ -624,7 +629,8 @@ async def simplecompare(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSiz usage = "[user]", category = "stats" ) - async def pehkui(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): + @commands.guild_only() + async def pehkui(self, ctx: GuildContext, *, who: MemberOrFakeOrSize = None): """Get your (or a user's) Pehkui scale. For use in the Pehkui Minecraft mod. Essentially a height represented in a unit of Steves.""" @@ -648,7 +654,8 @@ async def pehkui(self, ctx: BotContext, *, who: MemberOrFakeOrSize = None): usage = " [user]", category = "stats" ) - async def gravitycompare(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSize = None, + @commands.guild_only() + async def gravitycompare(self, ctx: GuildContext, memberOrHeight: MemberOrFakeOrSize = None, *, memberOrHeight2: MemberOrFakeOrSize = None): """ Compare two users' gravitation pull. @@ -681,7 +688,8 @@ async def gravitycompare(self, ctx: BotContext, memberOrHeight: MemberOrFakeOrSi usage = "[user]", category = "stats" ) - async def metal(self, ctx: BotContext, *, who: MemberOrFakeOrSize | WV = None): + @commands.guild_only() + async def metal(self, ctx: GuildContext, *, who: MemberOrFakeOrSize | WV = None): """Get the price of your weight in gold (and other metals!)""" if who is None: @@ -713,7 +721,8 @@ async def metal(self, ctx: BotContext, *, who: MemberOrFakeOrSize | WV = None): usage = "[value]", category = "stats" ) - async def convert(self, ctx: BotContext, *, whoOrWhat: MemberOrFakeOrSize | WV = None): + @commands.guild_only() + async def convert(self, ctx: GuildContext, *, whoOrWhat: MemberOrFakeOrSize | WV = None): """Convert from metric to US, or the other way around. #ALPHA# @@ -735,7 +744,7 @@ async def convert(self, ctx: BotContext, *, whoOrWhat: MemberOrFakeOrSize | WV = category = "stats" ) @commands.guild_only() - async def keypoints(self, ctx: BotContext, who: MemberOrFakeOrSize = None): + async def keypoints(self, ctx: GuildContext, who: MemberOrFakeOrSize = None): """See a users key points.""" if who is None: who = ctx.message.author @@ -752,7 +761,7 @@ async def keypoints(self, ctx: BotContext, who: MemberOrFakeOrSize = None): category = "stats" ) @commands.guild_only() - async def neuron(self, ctx: BotContext, who: MemberOrFakeOrSize = None): + async def neuron(self, ctx: GuildContext, who: MemberOrFakeOrSize = None): """How long would brain signals take to travel for a person?""" if who is None: who = ctx.message.author diff --git a/sizebot/cogs/thistracker.py b/sizebot/cogs/thistracker.py index dc358493..f830cf37 100644 --- a/sizebot/cogs/thistracker.py +++ b/sizebot/cogs/thistracker.py @@ -13,7 +13,7 @@ from sizebot import __version__ from sizebot.lib import paths from sizebot.lib.loglevels import EGG -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext logger = logging.getLogger("sizebot") @@ -91,7 +91,7 @@ def __init__(self, bot: commands.Bot): category = "misc" ) @commands.guild_only() - async def leaderboard(self, ctx: BotContext): + async def leaderboard(self, ctx: GuildContext): """See who's the most agreeable!""" logger.log(EGG, f"{ctx.message.author.display_name} found the leaderboard!") now = datetime.now(tzlocal()) diff --git a/sizebot/cogs/trigger.py b/sizebot/cogs/trigger.py index 9195abec..fc37187a 100644 --- a/sizebot/cogs/trigger.py +++ b/sizebot/cogs/trigger.py @@ -12,7 +12,7 @@ from sizebot.conf import conf from sizebot.lib import userdb, nickmanager from sizebot.lib.diff import Diff -from sizebot.lib.types import BotContext +from sizebot.lib.types import BotContext, GuildContext logger = logging.getLogger("sizebot") @@ -108,7 +108,8 @@ async def on_message(self, m: discord.Message): @commands.command( category = "trigger" ) - async def triggers(self, ctx: BotContext): + @commands.guild_only() + async def triggers(self, ctx: GuildContext): """List your trigger words.""" userid = ctx.author.id userdata = userdb.load(ctx.guild.id, userid) @@ -119,7 +120,8 @@ async def triggers(self, ctx: BotContext): @commands.command( category = "trigger" ) - async def exporttriggers(self, ctx: BotContext): + @commands.guild_only() + async def exporttriggers(self, ctx: GuildContext): """Export your trigger words.""" userid = ctx.author.id userdata = userdb.load(ctx.guild.id, userid) @@ -131,7 +133,8 @@ async def exporttriggers(self, ctx: BotContext): usage = " ", category = "trigger" ) - async def settrigger(self, ctx: BotContext, trigger: str, *, diff: Diff): + @commands.guild_only() + async def settrigger(self, ctx: GuildContext, trigger: str, *, diff: Diff): """Set a trigger word. #ALPHA# @@ -144,7 +147,8 @@ async def settrigger(self, ctx: BotContext, trigger: str, *, diff: Diff): category = "trigger", aliases = ["resettrigger", "unsettrigger", "removetrigger"] ) - async def cleartrigger(self, ctx: BotContext, *, trigger: str): + @commands.guild_only() + async def cleartrigger(self, ctx: GuildContext, *, trigger: str): """Remove a trigger word.""" unset_trigger(ctx.guild.id, ctx.author.id, trigger) await ctx.send(f"Removed trigger word {trigger!r}.") @@ -154,7 +158,8 @@ async def cleartrigger(self, ctx: BotContext, *, trigger: str): category = "trigger", aliases = ["resetalltriggers", "unsetalltriggers", "removealltriggers"] ) - async def clearalltriggers(self, ctx: BotContext): + @commands.guild_only() + async def clearalltriggers(self, ctx: GuildContext): """Remove all your trigger words.""" userdata = userdb.load(ctx.guild.id, ctx.author.id) for trigger in userdata.triggers.keys(): diff --git a/sizebot/cogs/winks.py b/sizebot/cogs/winks.py index cb7ee3e2..27c38746 100644 --- a/sizebot/cogs/winks.py +++ b/sizebot/cogs/winks.py @@ -67,6 +67,15 @@ class WinksCog(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot + @commands.command( + hidden = True, + category = "misc" + ) + async def winkcount(self, ctx: BotContext): + winkcount = get_winks() + await ctx.send(f"Yukio has winked {winkcount} times since 15 September, 2019! :wink:") + logger.info(f"Wink count requested by {ctx.author.nickname}! Current count: {winkcount} times!") + @commands.Cog.listener() async def on_message(self, message: discord.Message): if message.author.id != ids.yukio: @@ -82,15 +91,6 @@ async def on_message(self, message: discord.Message): if winkcount in milestones: await say_milestone(message.channel, winkcount) - @commands.command( - hidden = True, - category = "misc" - ) - async def winkcount(self, ctx: BotContext): - winkcount = get_winks() - await ctx.send(f"Yukio has winked {winkcount} times since 15 September, 2019! :wink:") - logger.info(f"Wink count requested by {ctx.author.nickname}! Current count: {winkcount} times!") - async def setup(bot: commands.Bot): await bot.add_cog(WinksCog(bot)) diff --git a/sizebot/extensions/banned.py b/sizebot/extensions/banned.py index 67d7e612..0c9ac65d 100644 --- a/sizebot/extensions/banned.py +++ b/sizebot/extensions/banned.py @@ -6,8 +6,7 @@ def den_guild_ban(ctx: BotContext) -> bool: member = ctx.author - isMember = isinstance(member, discord.Member) - if not isMember: + if not isinstance(member, discord.Member): return True is_guild_banned = discord.utils.get(member.roles, name = "SizeBot_Banned") is not None return not is_guild_banned diff --git a/sizebot/lib/checks.py b/sizebot/lib/checks.py index 229f845c..b0fa64ff 100644 --- a/sizebot/lib/checks.py +++ b/sizebot/lib/checks.py @@ -1,15 +1,16 @@ +from discord import Member from discord.ext import commands from sizebot.lib.types import BotContext -def is_mod() -> bool: +def is_mod(): async def predicate(ctx: BotContext) -> bool: - author = ctx.author - modness = False - if await ctx.bot.is_owner(author): - modness = True - elif author.permissions_in(ctx.channel).manage_guild: - modness = True - return modness + if not isinstance(ctx.author, Member): + return False + if await ctx.bot.is_owner(ctx.author): + return True + if ctx.channel.permissions_for(ctx.author).manage_guild: + return True + return False return commands.check(predicate) diff --git a/sizebot/lib/digidecimal.py b/sizebot/lib/digidecimal.py index cf7f36ed..a62f42b5 100644 --- a/sizebot/lib/digidecimal.py +++ b/sizebot/lib/digidecimal.py @@ -1,6 +1,6 @@ from __future__ import annotations from abc import abstractmethod -from typing import Self, cast, overload +from typing import Self, overload import numbers import decimal @@ -247,7 +247,7 @@ def __rmod__(self, other: BaseDecimal | int) -> BaseDecimal: rawvalue = unwrap_decimal(self) rawother = unwrap_decimal(other) if is_infinite(rawvalue): - return BaseDecimal(rawvalue) + return BaseDecimal(rawother) elif is_infinite(rawother): return BaseDecimal(0) return BaseDecimal(rawother % rawvalue) @@ -351,7 +351,12 @@ def to_integral_value(self, rounding: str | None = None) -> Self: def log10(self) -> BaseDecimal: rawvalue = unwrap_decimal(self) return BaseDecimal(rawvalue.log10()) - + + @abstractmethod + def sqrt(self) -> BaseDecimal: + rawvalue = unwrap_decimal(self) + return BaseDecimal(rawvalue.sqrt()) + def to_pydecimal(self) -> RawDecimal: return self._rawvalue diff --git a/sizebot/lib/freefall.py b/sizebot/lib/freefall.py index 730edc8f..8ac777ef 100644 --- a/sizebot/lib/freefall.py +++ b/sizebot/lib/freefall.py @@ -19,7 +19,7 @@ def freefall(basemass: WV, altitude: SV, scale: Decimal) -> Tuple[TV, SV]: Terminal velocity (m/s) """ basemass = basemass / Decimal(1000) - m = basemass * (scale ** Decimal(3)) + m = Decimal(basemass * (scale ** Decimal(3))) h = altitude g = Decimal("9.807") k = Decimal("0.24") * (scale ** Decimal(2)) diff --git a/sizebot/lib/macrovision.py b/sizebot/lib/macrovision.py index 6aeca257..515a763d 100644 --- a/sizebot/lib/macrovision.py +++ b/sizebot/lib/macrovision.py @@ -4,17 +4,12 @@ from dataclasses import dataclass import json import importlib.resources as pkg_resources -from json.decoder import JSONDecodeError -import aiohttp -from aiohttp_requests import requests import logging import sizebot.data -from sizebot.conf import conf from sizebot.lib.stats import StatBox from sizebot.lib.units import SV, Decimal from sizebot.lib.userdb import User -from sizebot.lib.utils import url_safe logger = logging.getLogger("sizebot") @@ -22,11 +17,11 @@ def get_model_scale(model: str, view: str, height_in_meters: SV) -> Decimal: - normal_height = Decimal(model_heights[model][view]) + normal_height = SV(model_heights[model][view]) return height_in_meters / normal_height -def get_entity_json(name: str, model: str, view: str, height: SV, x: float) -> Any: +def get_entity_json(name: str, model: str, view: str, height: SV, x: SV) -> Any: scale = get_model_scale(model, view, height) return { "name": model, @@ -40,33 +35,13 @@ def get_entity_json(name: str, model: str, view: str, height: SV, x: float) -> A } -async def shorten_url(url: str) -> str: - if not conf.cuttly_key: - return url - try: - r = await requests.get(f"https://cutt.ly/api/api.php?key={conf.cuttly_key}&short={url}", raise_for_status=True) - except aiohttp.ClientResponseError as e: - logger.error(f"Unable to shorten url: Status {e.status}") - return url - try: - cuttly_response = await r.json(content_type="text/html") - except (aiohttp.ContentTypeError): - logger.error(f"Unable to shorten url: Cannot parse JSON, bad content type: {r.content_type}") - return url - except (JSONDecodeError): - logger.error("Unable to shorten url: Cannot parse JSON") - return url - short_url = cuttly_response["url"]["shortLink"] - return short_url - - @dataclass class MacrovisionEntity(): name: str model: str - view: str | None + view: str height: SV - x: float = 0 + x: SV = SV(0) def user_to_entity(u: User) -> MacrovisionEntity: @@ -103,13 +78,9 @@ def get_url(entities: list[MacrovisionEntity]) -> str: world_height = entities[0].height - x_offset = Decimal(0) + x_offset = SV(0) for p in entities: - p.name = url_safe(p.name) - # Backwards compatibility - if p.view is None: - p.view = p.model - p.model = "Human" + p.name = p.name p.x = x_offset x_offset += p.height / 4 diff --git a/sizebot/lib/objs.py b/sizebot/lib/objs.py index e2c2a426..5ae87471 100644 --- a/sizebot/lib/objs.py +++ b/sizebot/lib/objs.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Literal, Any +from typing import Literal, Any, NotRequired, TypedDict, cast from functools import total_ordering import importlib.resources as pkg_resources @@ -12,7 +12,6 @@ import sizebot.data.objects from sizebot import __version__ from sizebot.lib import errors, userdb -from sizebot.lib.constants import emojis from sizebot.lib.language import get_plural, get_indefinite_article from sizebot.lib.types import BotContext from sizebot.lib.units import AV, SV, VV, WV, Unit, SystemUnit, Decimal @@ -26,27 +25,57 @@ Dimension = Literal["h", "l", "d", "w", "t", "p"] +class ObjectJson(TypedDict): + name: str + dimension: Dimension + aliases: NotRequired[list[str]] + tags: NotRequired[list[str]] + symbol: NotRequired[str] + height: NotRequired[str] # SV + length: NotRequired[str] # SV + width: NotRequired[str] # SV + diameter: NotRequired[str] # SV + depth: NotRequired[str] # SV + thickness: NotRequired[str] # SV + calories: NotRequired[str] # Decimal + price: NotRequired[str] # Decimal + weight: NotRequired[str] # WV + note: NotRequired[str] + + +class LandJson(TypedDict): + name: str + dimension: Dimension + aliases: NotRequired[list[str]] + tags: NotRequired[list[str]] + height: str # SV + length: str # SV + width: str # SV + note: NotRequired[str] + + @total_ordering class DigiObject: - def __init__(self, - name: str, - dimension: Dimension, - aliases: list[str] = [], - tags: list[str] = [], - symbol: str | None = None, - height: SV | None = None, - length: SV | None = None, - width: SV | None = None, - diameter: SV | None = None, - depth: SV | None = None, - thickness: SV | None = None, - calories: SV | None = None, - price: Decimal | None = None, - weight: WV | None = None, - note: str | None = None): - + def __init__( + self, + name: str, + dimension: Dimension, + aliases: list[str], + tags: list[str], + symbol: str | None, + height: SV | None, + length: SV | None, + width: SV | None, + diameter: SV | None, + depth: SV | None, + thickness: SV | None, + calories: Decimal | None, + price: Decimal | None, + weight: WV | None, + note: str | None + ): self.name = name - self.dimension = dimension + self.dimension: Dimension = dimension self.name_plural = get_plural(name) self.singular_names = aliases + [self.name] self.aliases = aliases + [get_plural(a) for a in aliases] @@ -55,197 +84,52 @@ def __init__(self, self._tags = tags self.tags = tags + [get_plural(t) for t in self._tags] self.article = get_indefinite_article(self.name).split(" ")[0] - self.symbol = symbol or None - self.note = note or None - - self.height = height and SV(height) - self.length = length and SV(length) - self.width = width and SV(width) - self.diameter = diameter and SV(diameter) - self.depth = depth and SV(depth) - self.thickness = thickness and SV(thickness) - self.calories = SV(calories) if calories is not None else None - self.price = Decimal(price) if price is not None else None - self.weight = weight and WV(weight) + self.symbol = symbol + self.note = note + + self.height = height + self.length = length + self.width = width + self.diameter = diameter + self.depth = depth + self.thickness = thickness + self.calories = calories + self.price = price + self.weight = weight + @property + def unitlength(self) -> SV: dimensionmap = { - "h": "height", - "l": "length", - "w": "width", - "d": "diameter", - "p": "depth", - "t": "thickness", - "c": "calories", + "h": self.height, + "l": self.length, + "w": self.width, + "d": self.diameter, + "p": self.depth, + "t": self.thickness, } - - self.unitlength: SV = getattr(self, dimensionmap[dimension]) - - @property - def image(self): - # TODO: See issue #153. - return None + return cast(SV, dimensionmap[self.dimension]) @property def area(self) -> AV | None: if self.height is not None and self.width is not None: - return AV(self.height * self.width) + return self.height * self.width elif self.length is not None and self.width is not None: - return AV(self.length * self.width) + return self.length * self.width elif self.diameter: r = self.diameter / 2 r2 = r ** 2 - return AV(Decimal(math.pi) * r2) + return Decimal(math.pi) * r2 return None @property def volume(self) -> VV | None: if self.area is not None: if self.depth is not None: - return VV(self.area * self.depth) + return self.area * self.depth elif self.thickness is not None: - return VV(self.area * self.thickness) + return self.area * self.thickness return None - def add_to_units(self): - if self.unitlength is not None: - u = Unit( - factor=Decimal(self.unitlength), - name=self.name, - namePlural=self.name_plural, - names=self.aliases, - symbol=self.symbol - ) - SV.add_unit(u) - SV.add_system_unit("o", SystemUnit(u)) - - if self.weight is not None: - u = Unit( - factor=Decimal(self.weight), - name=self.name, - namePlural=self.name_plural, - names=self.aliases, - symbol=self.symbol - ) - WV.add_unit(u) - WV.add_system_unit("o", SystemUnit(u)) - - def get_stats(self, multiplier: Decimal = 1) -> str: - returnstr = "" - if self.height: - returnstr += f"{emojis.blank}**{SV(self.height * multiplier):,.3mu}** tall\n" - if self.length: - returnstr += f"{emojis.blank}**{SV(self.length * multiplier):,.3mu}** long\n" - if self.width: - returnstr += f"{emojis.blank}**{SV(self.width * multiplier):,.3mu}** wide\n" - if self.diameter: - returnstr += f"{emojis.blank}**{SV(self.diameter * multiplier):,.3mu}** across\n" - if self.depth: - returnstr += f"{emojis.blank}**{SV(self.depth * multiplier):,.3mu}** deep\n" - if self.thickness: - returnstr += f"{emojis.blank}**{SV(self.thickness * multiplier):,.3mu}** thick\n" - if self.calories is not None: - returnstr += f"{emojis.blank}has **{Decimal(self.calories * (multiplier ** 3)):,.3}** calories\n" - if self.price is not None: - returnstr += f"{emojis.blank}costs **USD ${Decimal(self.price * (multiplier ** 3)):,.2f}**\n" - if self.weight: - returnstr += "and weighs...\n" - returnstr += f"{emojis.blank}**{WV(self.weight * (multiplier ** 3)):,.3mu}**" - return returnstr - - def get_stats_sentence(self, multiplier: Decimal = 1, system: Literal["m", "u"] = "m") -> str: - statsstrings = [] - if self.height: - statsstrings.append(f"**{SV(self.height * multiplier):,.3{system}}** tall") - if self.length: - statsstrings.append(f"**{SV(self.length * multiplier):,.3{system}}** long") - if self.width: - statsstrings.append(f"**{SV(self.width * multiplier):,.3{system}}** wide") - if self.diameter: - statsstrings.append(f"**{SV(self.diameter * multiplier):,.3{system}}** across") - if self.depth: - statsstrings.append(f"**{SV(self.depth * multiplier):,.3{system}}** deep") - if self.thickness: - statsstrings.append(f"**{SV(self.thickness * multiplier):,.3{system}}** thick") - if self.calories is not None: - statsstrings.append(f"has **{Decimal(self.calories * (multiplier ** 3)):,.3}** calories") - if self.price is not None: - statsstrings.append(f"costs **USD ${Decimal(self.price * (multiplier ** 3)):,.2f}**") - if self.weight: - statsstrings.append(f"weighs **{WV(self.weight * multiplier ** 3):,.3{system}}**") - - returnstr = sentence_join(statsstrings, oxford=True) + "." - - return returnstr - - def get_stats_embed(self, multiplier: Decimal = 1) -> Embed: - embed = Embed() - embed.set_author(name = f"SizeBot {__version__}") - - if self.height: - embed.add_field(name = "Height", - value = f"**{SV(self.height * multiplier):,.3mu}** tall\n") - if self.length: - embed.add_field(name = "Length", - value = f"**{SV(self.length * multiplier):,.3mu}** long\n") - if self.width: - embed.add_field(name = "Width", - value = f"**{SV(self.width * multiplier):,.3mu}** wide\n") - if self.diameter: - embed.add_field(name = "Diameter", - value = f"**{SV(self.diameter * multiplier):,.3mu}** across\n") - if self.depth: - embed.add_field(name = "Depth", - value = f"**{SV(self.depth * multiplier):,.3mu}** deep\n") - if self.thickness: - embed.add_field(name = "Thickness", - value = f"**{SV(self.thickness * multiplier):,.3mu}** thick\n") - if self.area is not None: - embed.add_field(name = "Area", - value = f"**{AV(self.area * (multiplier ** 2)):,.3mu}**\n") - if self.volume is not None: - embed.add_field(name = "Volume", - value = f"**{VV(self.volume * (multiplier ** 3)):,.3mu}**\n") - if self.calories is not None: - embed.add_field(name = "Calories", - value = f"**{Decimal(self.calories * (multiplier ** 3)):,.3}** calories\n") - if self.price is not None: - embed.add_field(name = "Price", - value = f"**${Decimal(self.price * (multiplier ** 3)):,.2}**") - if self.weight: - embed.add_field(name = "Weight", - value = f"**{WV(self.weight * (multiplier ** 3)):,.3mu}**") - - if self.image: - embed.set_image(self.image) - - return embed - - def stats(self) -> str: - return f"{self.article.capitalize()} {self.name} is...\n" + self.get_stats() - - def statsembed(self) -> Embed: - embed = self.get_stats_embed() - embed.title = self.name - embed.description = f"*{self.note}*" if self.note else None - return embed - - def relativestats(self, userdata: userdb.User) -> str: - return (f"__{userdata.nickname} is {userdata.height:,.3mu} tall.__\n" - f"To {userdata.nickname}, {self.article} {self.name} looks...\n") \ - + self.get_stats(userdata.viewscale) - - def relativestatssentence(self, userdata: userdb.User) -> str: - return (f"{userdata.nickname} is {userdata.height:,.3{userdata.unitsystem}} tall." - f" To them, {self.article} {self.name} looks ") \ - + self.get_stats_sentence(userdata.viewscale, userdata.unitsystem) - - def relativestatsembed(self, userdata: userdb.User) -> Embed: - embed = self.get_stats_embed(userdata.viewscale) - embed.title = self.name + " *[relative]*" - embed.description = (f"__{userdata.nickname} is {userdata.height:,.3mu} tall.__\n" - f"To {userdata.nickname}, {self.article} {self.name} looks...\n") - return embed - def __eq__(self, other: Any) -> bool: if isinstance(other, str): lowerName = other.lower() @@ -274,8 +158,23 @@ def find_by_name(cls, name: str) -> DigiObject | None: return None @classmethod - def from_JSON(cls, objJson: Any) -> DigiObject: - return cls(**objJson) + def from_json(cls, data: ObjectJson) -> DigiObject: + name = data["name"] + dimension = data["dimension"] + aliases = data.get("aliases", []) + tags = data.get("tags", []) + symbol = data.get("symbol") + height = SV(data["height"]) if "height" in data else None + length = SV(data["length"]) if "length" in data else None + width = SV(data["width"]) if "width" in data else None + diameter = SV(data["diameter"]) if "diameter" in data else None + depth = SV(data["depth"]) if "depth" in data else None + thickness = SV(data["thickness"]) if "thickness" in data else None + calories = Decimal(data["calories"]) if "calories" in data else None + price = Decimal(data["price"]) if "price" in data else None + weight = WV(data["weight"]) if "weight" in data else None + note = data.get("note") + return cls(name, dimension, aliases, tags, symbol, height, length, width, diameter, depth, thickness, calories, price, weight, note) @classmethod async def convert(cls, ctx: BotContext, argument: str) -> DigiObject: @@ -291,17 +190,159 @@ def __repr__(self) -> str: return str(self) +class DigiLand: + def __init__( + self, + name: str, + dimension: Dimension, + height: SV, + length: SV, + width: SV, + tags: list[str], + note: str | None + ): + self.name = name + self.dimension = dimension + self.height = height + self.length = length + self.width = width + self.tags = tags + self.note = note + + @property + def area(self) -> AV: + return self.height * self.width + + @classmethod + def from_digiobject(cls, obj: DigiObject) -> DigiLand: + return cls( + name=obj.name, + dimension=obj.dimension, + height=cast(SV, obj.height), + length=cast(SV, obj.length), + width=cast(SV, obj.width), + tags=obj.tags, + note=obj.note + ) + + + + +def _get_stats_embed(obj: DigiObject, multiplier: Decimal = Decimal(1)) -> Embed: + embed = Embed() + embed.set_author(name = f"SizeBot {__version__}") + + if obj.height: + embed.add_field(name = "Height", + value = f"**{SV(obj.height * multiplier):,.3mu}** tall\n") + if obj.length: + embed.add_field(name = "Length", + value = f"**{SV(obj.length * multiplier):,.3mu}** long\n") + if obj.width: + embed.add_field(name = "Width", + value = f"**{SV(obj.width * multiplier):,.3mu}** wide\n") + if obj.diameter: + embed.add_field(name = "Diameter", + value = f"**{SV(obj.diameter * multiplier):,.3mu}** across\n") + if obj.depth: + embed.add_field(name = "Depth", + value = f"**{SV(obj.depth * multiplier):,.3mu}** deep\n") + if obj.thickness: + embed.add_field(name = "Thickness", + value = f"**{SV(obj.thickness * multiplier):,.3mu}** thick\n") + if obj.area is not None: + embed.add_field(name = "Area", + value = f"**{AV(obj.area * (multiplier ** 2)):,.3mu}**\n") + if obj.volume is not None: + embed.add_field(name = "Volume", + value = f"**{VV(obj.volume * (multiplier ** 3)):,.3mu}**\n") + if obj.calories is not None: + embed.add_field(name = "Calories", + value = f"**{Decimal(obj.calories * (multiplier ** 3)):,.3}** calories\n") + if obj.price is not None: + embed.add_field(name = "Price", + value = f"**${Decimal(obj.price * (multiplier ** 3)):,.2}**") + if obj.weight: + embed.add_field(name = "Weight", + value = f"**{WV(obj.weight * (multiplier ** 3)):,.3mu}**") + + return embed + +def relativestatsembed(obj: DigiObject, userdata: userdb.User) -> Embed: + embed = _get_stats_embed(obj, userdata.viewscale) + embed.title = obj.name + " *[relative]*" + embed.description = (f"__{userdata.nickname} is {userdata.height:,.3mu} tall.__\n" + f"To {userdata.nickname}, {obj.article} {obj.name} looks...\n") + return embed + +def relativestatssentence(obj: DigiObject, userdata: userdb.User) -> str: + return (f"{userdata.nickname} is {userdata.height:,.3{userdata.unitsystem}} tall." + f" To them, {obj.article} {obj.name} looks ") \ + + get_stats_sentence(obj, userdata.viewscale, userdata.unitsystem) + +def statsembed(obj: DigiObject) -> Embed: + embed = _get_stats_embed(obj) + embed.title = obj.name + embed.description = f"*{obj.note}*" if obj.note else None + return embed + +def get_stats_sentence(obj: DigiObject, multiplier: Decimal, system: Literal["m", "u"]) -> str: + statsstrings: list[str] = [] + if obj.height: + statsstrings.append(f"**{SV(obj.height * multiplier):,.3{system}}** tall") + if obj.length: + statsstrings.append(f"**{SV(obj.length * multiplier):,.3{system}}** long") + if obj.width: + statsstrings.append(f"**{SV(obj.width * multiplier):,.3{system}}** wide") + if obj.diameter: + statsstrings.append(f"**{SV(obj.diameter * multiplier):,.3{system}}** across") + if obj.depth: + statsstrings.append(f"**{SV(obj.depth * multiplier):,.3{system}}** deep") + if obj.thickness: + statsstrings.append(f"**{SV(obj.thickness * multiplier):,.3{system}}** thick") + if obj.calories is not None: + statsstrings.append(f"has **{Decimal(obj.calories * (multiplier ** 3)):,.3}** calories") + if obj.price is not None: + statsstrings.append(f"costs **USD ${Decimal(obj.price * (multiplier ** 3)):,.2f}**") + if obj.weight: + statsstrings.append(f"weighs **{WV(obj.weight * multiplier ** 3):,.3{system}}**") + + returnstr = sentence_join(statsstrings, oxford=True) + "." + + return returnstr + +def add_obj_to_units(obj: DigiObject): + if obj.unitlength is not None: + u = Unit( + factor=Decimal(obj.unitlength), + name=obj.name, + namePlural=obj.name_plural, + names=obj.aliases, + symbol=obj.symbol + ) + SV.add_unit(u) + SV.add_system_unit("o", SystemUnit(u)) + + if obj.weight is not None: + u = Unit( + factor=Decimal(obj.weight), + name=obj.name, + namePlural=obj.name_plural, + names=obj.aliases, + symbol=obj.symbol + ) + WV.add_unit(u) + WV.add_system_unit("o", SystemUnit(u)) + + def load_obj_file(filename: str): - try: - fileJson = json.loads(pkg_resources.read_text(sizebot.data.objects, filename)) - except FileNotFoundError: - fileJson = None - load_obj_JSON(fileJson) + fileJson = json.loads(pkg_resources.read_text(sizebot.data.objects, filename)) + load_obj_json(fileJson) -def load_obj_JSON(fileJson: Any): - for objJson in fileJson: - objects.append(DigiObject.from_JSON(objJson)) +def load_obj_json(data: list[ObjectJson]): + for d in data: + objects.append(DigiObject.from_json(d)) def init(): @@ -313,7 +354,7 @@ def init(): objects.sort() for o in objects: - o.add_to_units() + add_obj_to_units(o) # cached values food = [o for o in objects if "food" in o.tags] @@ -331,35 +372,34 @@ def get_close_object_smart(val: SV | WV) -> DigiObject: Tries to get a single object for comparison, prioritizing integer closeness. """ - best_dict: dict[float, list] = { - 1.0: [], - 0.5: [], - 2.0: [], - 1.5: [], - 3.0: [], - 2.5: [], - 4.0: [], - 5.0: [], - 6.0: [] + best_dict: dict[Decimal, list[tuple[Decimal, tuple[Decimal, Decimal], DigiObject]]] = { + Decimal(0.5): [], + Decimal(1): [], + Decimal(1.5): [], + Decimal(2): [], + Decimal(2.5): [], + Decimal(3): [], + Decimal(4): [], + Decimal(5): [], + Decimal(6): [] } - weight = isinstance(val, WV) - - val = float(val) - - dists = [] + dists: list[tuple[Decimal, tuple[Decimal, Decimal], DigiObject]] = [] for obj in objects: - if weight and not obj.weight: - continue if "nc" in obj.tags: continue - ratio = val / float(obj.unitlength) if not weight else val / float(obj.weight) + if isinstance(val, WV) and obj.weight is not None: + ratio = val / obj.weight + elif isinstance(val, SV): + ratio = val / obj.unitlength + else: + continue ratio_semi = round(ratio, 1) rounded_ratio = round(ratio) - intness = 2.0 * (ratio - rounded_ratio) + intness = 2 * (ratio - rounded_ratio) - oneness = (1.0 - ratio if ratio > 1.0 else 1.0 / ratio - 1.0) + oneness = (1 - ratio if ratio > 1 else 1 / ratio - 1) dist = intness**2 + oneness**2 @@ -369,7 +409,7 @@ def get_close_object_smart(val: SV | WV) -> DigiObject: else: dists.append(p) - best = [] + best: list[tuple[Decimal, tuple[Decimal, Decimal], DigiObject]] = [] for at_val in best_dict.values(): best.extend(at_val) if len(best) >= 10: @@ -383,7 +423,12 @@ def get_close_object_smart(val: SV | WV) -> DigiObject: def format_close_object_smart(val: SV | WV) -> str: - weight = isinstance(val, WV) obj = get_close_object_smart(val) - ans = round(val / obj.unitlength, 1) if not weight else round(val / obj.weight, 1) + if isinstance(val, WV) and obj.weight is not None: + ans = round(val / obj.weight, 1) + elif isinstance(val, SV): + ans = round(val / obj.unitlength, 1) + else: + raise TypeError + return f"{ans:.1f} {obj.name_plural if ans != 1 else obj.name}" diff --git a/sizebot/lib/shoesize.py b/sizebot/lib/shoesize.py index 31db8937..f9760eb5 100644 --- a/sizebot/lib/shoesize.py +++ b/sizebot/lib/shoesize.py @@ -1,5 +1,6 @@ import re +from sizebot.lib.errors import InvalidSizeValue from sizebot.lib.gender import Gender from sizebot.lib.units import SV, Decimal @@ -7,7 +8,7 @@ def to_shoe_size(footlength: SV, gender: Gender) -> str: women = gender == "f" # Inch in meters - inch = Decimal("0.0254") + inch = SV("0.0254") footlengthinches = footlength / inch shoesizeNum = (3 * (footlengthinches + Decimal("2/3"))) - 24 prefix = "" @@ -27,7 +28,10 @@ def to_shoe_size(footlength: SV, gender: Gender) -> str: def from_shoe_size(shoesize: str) -> SV: - shoesizenum = unmodifiedshoesizenum = Decimal(re.search(r"(\d*,)*\d+(\.\d*)?", shoesize)[0]) + parsed = re.search(r"(\d*,)*\d+(\.\d*)?", shoesize) + if parsed is None: + raise InvalidSizeValue(shoesize, "shoesize") + shoesizenum = unmodifiedshoesizenum = Decimal(parsed[0]) if "w" in shoesize.lower(): shoesizenum = unmodifiedshoesizenum - 1 if "c" in shoesize.lower(): # Intentional override, children's sizes have no women/men distinction. diff --git a/sizebot/lib/stats.py b/sizebot/lib/stats.py index fb9fec86..b38dccb8 100644 --- a/sizebot/lib/stats.py +++ b/sizebot/lib/stats.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Any, TypeVar, TypedDict +from typing import Any, TypeVar, TypedDict, cast from collections.abc import Callable from functools import cached_property @@ -8,7 +8,7 @@ from sizebot.lib import errors from sizebot.lib.constants import emojis from sizebot.lib.gender import Gender -from sizebot.lib.units import SV, TV, WV, AV, Decimal +from sizebot.lib.units import Decimal, SV, WV, TV, AV from sizebot.lib.shoesize import to_shoe_size from sizebot.lib.surface import can_walk_on_water @@ -85,16 +85,16 @@ def wrapped(sb: StatBox) -> str: return f -def wrap_bool(f: bool | Callable[[ValueDict], bool]) -> Callable[[ValueDict], bool]: +def wrap_bool(f: bool | Callable[[StatBox], bool]) -> Callable[[StatBox], bool]: if isinstance(f, bool): - def wrapped(values: ValueDict) -> bool: + def wrapped(sb: StatBox) -> bool: return f return wrapped else: return f -def grouped_z(z: int) -> tuple[int, int]: +def grouped_z(z: int | None) -> tuple[int, int]: if z is None: return (1, 0) elif z < 0: @@ -130,12 +130,12 @@ def __init__(self, userkey: str | None = None, value: Callable[[ValueDict], Any] | None = None, power: int | None = None, - requires: list[str] = None, + requires: list[str] | None = None, type: Callable[[Any], Any] | None = None, z: int | None = None, - tags: list[str] = None, + tags: list[str] | None = None, inline: bool = True, - aliases: list[str] = None + aliases: list[str] | None = None ): if userkey is not None and power is None: raise Exception(f'StatDef("{key}") with userkey must have power set') @@ -174,7 +174,7 @@ def load_stat(self, value = self.get_value(values) elif self.userkey is not None: # load from userstats - value = userstats[self.userkey] + value: Any = userstats[self.userkey] if value is not None: is_setbyuser = True elif self.get_value is not None: @@ -191,7 +191,7 @@ def scale_stat(self, sb: StatBox, values: dict[str, Any], *, - scale: Decimal = 1, + scale: Decimal = Decimal(1), old_value: Any = None, is_setbyuser: bool ) -> None | Stat: @@ -208,11 +208,13 @@ def scale_stat(self, else: # Scale by the power value = old_value * (scale ** self.power) - else: + elif self.get_value is not None: # calculate from value() if any(r not in values for r in self.requires): return value = self.get_value(values) + else: + return return Stat(sb, self, self.type(value), is_setbyuser) @@ -256,7 +258,7 @@ def string(self) -> str: return self.definition.get_string(self.sb) @cached_property - def embed(self) -> dict: + def embed(self) -> dict[str, str | bool]: return { "name": self.title, "value": self.body, @@ -333,7 +335,12 @@ def load_average(cls) -> StatBox: "taillength": None, "earheight": None, "macrovision_model": None, - "macrovision_view": None + "macrovision_view": None, + "footlength": None, + "liftstrength": None, + "walkperhour": None, + "swimperhour": None, + "runperhour": None } return cls.load(average_userdata) @@ -813,7 +820,7 @@ def bool_to_icon(value: bool) -> str: value=lambda v: v["terminalvelocity"] < FALL_LIMIT), StatDef("fallprooficon", title="Fallproof Icon", - string=lambda s: bool_to_icon(s['fallproof']), + string=lambda s: bool_to_icon(cast(bool, s['fallproof'])), body=lambda s: bool_to_icon(s['fallproof'].value), is_shown=False, requires=["fallproof"], @@ -1096,7 +1103,7 @@ def bool_to_icon(value: bool) -> str: requires=["height", "width", "weight"], type=SV, is_shown=False, - value=lambda v: SV(math.sqrt(((v["weight"] / 1000) * GRAVITY) / (v["height"] * v["width"] * AIR_DENSITY)) * 60 * 60), + value=lambda v: SV(math.sqrt(Decimal((v["weight"] / 1000) * GRAVITY) / Decimal(v["height"] * v["width"] * AIR_DENSITY)) * 60 * 60), tags=["speed"], aliases=["blow"]), StatDef("percievedvolume", @@ -1128,7 +1135,7 @@ def bool_to_icon(value: bool) -> str: def generate_statmap() -> dict[str, str]: - statmap = {} + statmap: dict[str, str] = {} # Load all keys for stat in all_stats: if stat.key in statmap: @@ -1144,7 +1151,7 @@ def generate_statmap() -> dict[str, str]: def generate_taglist() -> list[str]: - tags = [] + tags: list[str] = [] for stat in all_stats: if stat.tags: tags.extend(stat.tags) @@ -1171,8 +1178,8 @@ def calc_view_angle(viewer: SV, viewee: SV) -> Decimal: # TODO: CamelCase def calcHorizon(height: SV) -> SV: - EARTH_RADIUS = 6378137 - return SV(math.sqrt((EARTH_RADIUS + height) ** 2 - EARTH_RADIUS ** 2)) + EARTH_RADIUS = SV(6378137) + return ((EARTH_RADIUS + height) ** 2 - EARTH_RADIUS ** 2).sqrt() # TODO: CamelCase diff --git a/sizebot/lib/types.py b/sizebot/lib/types.py index 003998c9..b49a98a7 100644 --- a/sizebot/lib/types.py +++ b/sizebot/lib/types.py @@ -1,9 +1,21 @@ -from typing import TypedDict +from typing import TypedDict, cast +import discord.utils +from discord import Guild, Member from discord.ext import commands -BotContext = commands.Context[commands.Bot] +class GuildContext(commands.Context[commands.Bot]): + @discord.utils.cached_property + def guild(self) -> Guild: + """:class:`.Guild`: Returns the guild associated with this context's command. None if not available.""" + return cast(Guild, self.message.guild) + + @discord.utils.cached_property + def author(self) -> Member: + """:class:`.Guild`: Returns the guild associated with this context's command. None if not available.""" + return cast(Member, self.message.author) +BotContext = commands.Context[commands.Bot] class EmbedToSend(TypedDict): embed: str diff --git a/sizebot/lib/units.py b/sizebot/lib/units.py index e0e0da49..80471f74 100644 --- a/sizebot/lib/units.py +++ b/sizebot/lib/units.py @@ -670,19 +670,26 @@ def __rdivmod__(self, other: Decimal | int) -> tuple[Decimal, Decimal]: return Decimal(quotient), Decimal(remainder) raise NotImplementedError + # Decimal ** Decimal = Decimal def __pow__(self, other: Decimal | int) -> Decimal: if isinstance(other, Decimal | int): return Decimal(super().__pow__(other)) raise NotImplementedError + # Decimal ** Decimal = Decimal def __rpow__(self, other: Decimal | int) -> Decimal: if isinstance(other, Decimal | int): return Decimal(super().__rpow__(other)) raise NotImplementedError + # Decimal.log10() = Decimal def log10(self) -> Decimal: return Decimal(super().log10()) + # Decimal.sqrt() = Decimal + def sqrt(self) -> Decimal: + return Decimal(super().sqrt()) + class SV(Dimension): """Size Value (length in meters)""" @@ -811,7 +818,7 @@ def __rmul__(self, other: SV | AV | Decimal | int) -> SV | AV | VV: # SV / Decimal = SV @overload - def __truediv__(self, other: Decimal) -> SV: + def __truediv__(self, other: Decimal | int) -> SV: ... # SV / SV = Decimal @@ -943,6 +950,10 @@ def __rpow__(self, other: Never) -> Never: def log10(self) -> Decimal: return Decimal(super().log10()) + # SV.sqrt() = NotImplementedError + def sqrt(self) -> Never: + raise NotImplementedError + class WV(Dimension): """Weight Value (mass in grams)""" @@ -1095,10 +1106,14 @@ def __pow__(self, other: Literal[0, 1]) -> Decimal | WV: def __rpow__(self, other: Never) -> Never: raise NotImplementedError - # SV.log10() = Decimal + # WV.log10() = Decimal def log10(self) -> Decimal: return Decimal(super().log10()) + # WV.sqrt() = NotImplementedError + def sqrt(self) -> Never: + raise NotImplementedError + class TV(Dimension): """Time Value (time in seconds)""" @@ -1253,10 +1268,14 @@ def __pow__(self, other: Literal[0, 1]) -> Decimal | TV: def __rpow__(self, other: Never) -> Never: raise NotImplementedError - # SV.log10() = Decimal + # TV.log10() = Decimal def log10(self) -> Decimal: return Decimal(super().log10()) + # TV.sqrt() = NotImplementedError + def sqrt(self) -> Never: + raise NotImplementedError + class AV(Dimension): """Area Value (area in meters-squared)""" @@ -1430,10 +1449,14 @@ def __pow__(self, other: Literal[0, 1]) -> Decimal | AV: def __rpow__(self, other: Never) -> Never: raise NotImplementedError - # SV.log10() = Decimal + # AV.log10() = Decimal def log10(self) -> Decimal: return Decimal(super().log10()) + # AV.sqrt() = SV + def sqrt(self) -> SV: + return SV(super().sqrt()) + class VV(Dimension): """Area Value (area in meters-cubed)""" @@ -1580,10 +1603,14 @@ def __pow__(self, other: Literal[0, 1]) -> Decimal | VV: def __rpow__(self, other: Never) -> Never: raise NotImplementedError - # SV.log10() = Decimal + # VV.log10() = Decimal def log10(self) -> Decimal: return Decimal(super().log10()) + # VV.sqrt() = NotImplementedError + def sqrt(self) -> Never: + raise NotImplementedError + class RV(Dimension): """Speed Value (meters per second)""" @@ -1740,10 +1767,14 @@ def __pow__(self, other: Literal[0, 1]) -> Decimal | RV: def __rpow__(self, other: Never) -> Never: raise NotImplementedError - # SV.log10() = Decimal + # RV.log10() = Decimal def log10(self) -> Decimal: return Decimal(super().log10()) + # RV.sqrt() = NotImplementedError + def sqrt(self) -> Never: + raise NotImplementedError + INCH = Decimal("0.0254") diff --git a/sizebot/lib/utils.py b/sizebot/lib/utils.py index e1cf043f..019464a4 100644 --- a/sizebot/lib/utils.py +++ b/sizebot/lib/utils.py @@ -17,62 +17,47 @@ from sizebot.lib.units import Decimal +SECOND = 1000 +MINUTE = 60 * SECOND +HOUR = 60 * MINUTE +DAY = 24 * HOUR +YEAR = 365 * DAY + +timeunits = [ + # min max unit name frac + (YEAR, None, YEAR, "year", False), + (DAY, 1_000_000 * YEAR, DAY, "day", False), + (HOUR, 1000 * YEAR, HOUR, "hour", False), + (MINUTE, YEAR, MINUTE, "minute", False), + (None, DAY, SECOND, "second", True) +] + def pretty_time_delta(totalSeconds: Decimal, millisecondAccuracy: bool = False, roundeventually: bool = False) -> str: """Get a human readable string representing an amount of time passed.""" - MILLISECONDS_PER_YEAR = 86400 * 365 * 1000 - MILLISECONDS_PER_DAY = 86400 * 1000 - MILLISECONDS_PER_HOUR = 3600 * 1000 - MILLISECONDS_PER_MINUTE = 60 * 1000 - MILLISECONDS_PER_SECOND = 1000 - - inputms = milliseconds = int(totalSeconds * 1000) - years, milliseconds = divmod(milliseconds, MILLISECONDS_PER_YEAR) - days, milliseconds = divmod(milliseconds, MILLISECONDS_PER_DAY) - hours, milliseconds = divmod(milliseconds, MILLISECONDS_PER_HOUR) - minutes, milliseconds = divmod(milliseconds, MILLISECONDS_PER_MINUTE) - seconds, milliseconds = divmod(milliseconds, MILLISECONDS_PER_SECOND) + + duration = int(totalSeconds * 1000) s = "" - if not roundeventually or inputms <= MILLISECONDS_PER_DAY: - if inputms >= MILLISECONDS_PER_YEAR: - s += f"{years:,d} year{'s' if years != 1 else ''}, " - if inputms >= MILLISECONDS_PER_DAY: - s += f"{days:,d} day{'s' if days != 1 else ''}, " - if inputms >= MILLISECONDS_PER_HOUR: - s += f"{hours:,d} hour{'s' if hours != 1 else ''}, " - if inputms >= MILLISECONDS_PER_MINUTE: - s += f"{minutes:,d} minute{'s' if minutes != 1 else ''}, " - if millisecondAccuracy: - s += f"{seconds:,d}.{milliseconds:03d} second{'' if seconds == 1 and milliseconds == 0 else 's'}" - else: - s += f"{seconds:,d} second{'s' if seconds != 1 else ''}" - elif inputms >= MILLISECONDS_PER_YEAR * 1_000_000: - if inputms >= MILLISECONDS_PER_YEAR: - s += f"{years:,d} year{'s' if years != 1 else ''}" - elif inputms >= MILLISECONDS_PER_YEAR * 1000: - if inputms >= MILLISECONDS_PER_YEAR: - s += f"{years:,d} year{'s' if years != 1 else ''}, " - if inputms >= MILLISECONDS_PER_DAY: - s += f"{days:,d} day{'s' if days != 1 else ''}" - elif inputms >= MILLISECONDS_PER_YEAR: - if inputms >= MILLISECONDS_PER_YEAR: - s += f"{years:,d} year{'s' if years != 1 else ''}, " - if inputms >= MILLISECONDS_PER_DAY: - s += f"{days:,d} day{'s' if days != 1 else ''}, " - if inputms >= MILLISECONDS_PER_HOUR: - s += f"{hours:,d} hour{'s' if hours != 1 else ''}" - elif inputms >= MILLISECONDS_PER_DAY: - if inputms >= MILLISECONDS_PER_YEAR: - s += f"{years:,d} year{'s' if years != 1 else ''}, " - if inputms >= MILLISECONDS_PER_DAY: - s += f"{days:,d} day{'s' if days != 1 else ''}, " - if inputms >= MILLISECONDS_PER_HOUR: - s += f"{hours:,d} hour{'s' if hours != 1 else ''}, " - if inputms >= MILLISECONDS_PER_MINUTE: - s += f"{minutes:,d} minute{'s' if minutes != 1 else ''}" + remaining = duration + for mintime, maxtime, unit, name, fraction in timeunits: + value, remaining = divmod(remaining, unit) + if mintime is not None and duration < mintime: + continue + if roundeventually and maxtime is not None and duration >= maxtime: + continue + if millisecondAccuracy and fraction: + s += f"{value:,d}.{remaining:03d} second{_s(value, remaining)}" + else: + s += f"{value:,d} {name}{_s(value)}" return s +def _s(value: int, fraction: int = 0) -> str: + if value != 1 or fraction != 0: + return "s" + else: + return "" + def try_int(val: Any) -> Any: """Try to cast `val` to an `int`, if it can't, just return `val`.""" diff --git a/tests/test_infinite.py b/tests/test_infinite.py index 09a1a9d8..704c8370 100644 --- a/tests/test_infinite.py +++ b/tests/test_infinite.py @@ -22,7 +22,7 @@ def test_pos__repr__(): def test_neg__repr__(): result = repr(neginf) - assert result == "Decimal('-∞')" + assert result == "BaseDecimal('-∞')" def test_pos__bool__(): diff --git a/tests/test_macrovision.py b/tests/test_macrovision.py index 00b511fd..bff1b9f1 100644 --- a/tests/test_macrovision.py +++ b/tests/test_macrovision.py @@ -33,8 +33,8 @@ async def test_macrovision(): expected_url = f"https://macrovision.crux.sexy/?scene={expected_base64}" macrovision_url = macrovision.get_url( [ - macrovision.MacrovisionEntity(name="Duncan", model="male", view=None, height=SV("0.0127")), - macrovision.MacrovisionEntity(name="Natalie", model="female", view=None, height=SV("0.1524")) + macrovision.MacrovisionEntity(name="Duncan", model="Human", view="male", height=SV("0.0127")), + macrovision.MacrovisionEntity(name="Natalie", model="Human", view="female", height=SV("0.1524")) ] ) assert macrovision_url == expected_url @@ -53,8 +53,8 @@ async def test_weird_names(): expected_url = f"https://macrovision.crux.sexy/?scene={expected_base64}" macrovision_url = macrovision.get_url( [ - macrovision.MacrovisionEntity(name=r"r'(?