diff options
Diffstat (limited to 'bot/seasons')
| -rw-r--r-- | bot/seasons/valentines/lovecalculator.py | 97 |
1 files changed, 66 insertions, 31 deletions
diff --git a/bot/seasons/valentines/lovecalculator.py b/bot/seasons/valentines/lovecalculator.py index b995aef4..4df33b93 100644 --- a/bot/seasons/valentines/lovecalculator.py +++ b/bot/seasons/valentines/lovecalculator.py @@ -1,70 +1,105 @@ +import bisect +import hashlib import json import logging +import random from pathlib import Path -from random import choice +from typing import Union import discord +from discord import Member from discord.ext import commands +from discord.ext.commands import BadArgument, clean_content from bot.constants import Roles log = logging.getLogger(__name__) -with open(Path("bot", "resources", "valentines", "love_matches.json"), "r") as file: +with Path('bot', 'resources', 'valentines', 'love_matches.json').open() as file: LOVE_DATA = json.load(file) -LOVE_LEVELS = [int(x) for x in LOVE_DATA] + LOVE_DATA = sorted((int(key), value) for key, value in LOVE_DATA.items()) class LoveCalculator: """ A cog for calculating the love between two people """ + def __init__(self, bot): self.bot = bot @commands.command(aliases=('love_calculator', 'love_calc')) - @commands.cooldown(rate=1, per=5.0, type=commands.BucketType.user) - async def love(self, ctx, name_one: discord.Member, name_two: discord.Member = None): + @commands.cooldown(rate=1, per=5, type=commands.BucketType.user) + async def love(self, ctx, who: Union[Member, str], whom: Union[Member, str] = None): """ - Calculates the love between two given names + Tells you how much the two love each other. + + This command accepts users or arbitrary strings as arguments. + Users are converted from: + - User ID + - Mention + - name#discrim + - name + - nickname - DO NOT SPAM @mentions! There are five ways to hand over a name to this command: - 1. ID of the user - 2. Name + Discriminator of the user (name#discrim) (1) - 3. Username (1) - 4. Nickname (1) - 5. @mention + Any two arguments will always yield the same result, though the order of arguments matters: + Running .love joseph erlang will always yield the same result. + Running .love erlang joseph won't yield the same result as .love joseph erlang - Using method 1-4 is highly encouraged, as nobody likes unwanted pings + they'll count as spam. - Skipping the second name will lead to something awesome. + If you want to use multiple words for one argument, you must include quotes. + .love "Zes Vappa" "morning coffee" - *(1): If the name has any form of spacing, the name must be wrapped inside quotes. Example: - .love Ves Zappa Niko Laus -> Will not work - .love "Ves Zappa" "Niko Laus" -> Will work + If only one argument is provided, the subject will become one of the helpers at random. """ - if name_two is None: + if whom is None: staff = ctx.guild.get_role(Roles.helpers).members - name_two = choice(staff) - - love_meter = (name_one.id + name_two.id) % 101 - love_idx = str(sorted(x for x in LOVE_LEVELS if x <= love_meter)[-1]) - love_status = choice(LOVE_DATA[love_idx]["titles"]) - + whom = random.choice(staff) + + def normalize(arg): + if isinstance(arg, Member): + # if we are given a member, return name#discrim without any extra changes + arg = str(arg) + else: + # otherwise normalise case and remove any leading/trailing whitespace + arg = arg.strip().title() + # this has to be done manually to be applied to usernames + return clean_content(escape_markdown=True).convert(ctx, arg) + + who, whom = [await normalize(arg) for arg in (who, whom)] + + # make sure user didn't provide something silly such as 10 spaces + if not (who and whom): + raise BadArgument('Arguments be non-empty strings.') + + # hash inputs to guarantee consistent results (hashing algorithm choice arbitrary) + # + # hashlib is used over the builtin hash() function + # to guarantee same result over multiple runtimes + m = hashlib.sha256(who.encode() + whom.encode()) + # mod 101 for [0, 100] + love_percent = sum(m.digest()) % 101 + + # We need the -1 due to how bisect returns the point + # see the documentation for further detail + # https://docs.python.org/3/library/bisect.html#bisect.bisect + index = bisect.bisect(LOVE_DATA, (love_percent,)) - 1 + # we already have the nearest "fit" love level + # we only need the dict, so we can ditch the first element + _, data = LOVE_DATA[index] + + status = random.choice(data['titles']) embed = discord.Embed( - title=love_status, - description=( - f'{name_one.display_name} \u2764 {name_two.display_name}' - f' scored {love_meter}%!\n\u200b' - ), + title=status, + description=f'{who} \N{HEAVY BLACK HEART} {whom} scored {love_percent}%!\n\u200b', color=discord.Color.dark_magenta() ) embed.add_field( name='A letter from Dr. Love:', - value=LOVE_DATA[love_idx]["text"] + value=data['text'] ) - await ctx.message.channel.send(embed=embed) + await ctx.send(embed=embed) def setup(bot): |