import asyncio import discord import logging import re from random import randint from random import choice as randchoice from redbot.core import bank, checks, commands, Config from redbot.core.errors import BalanceTooHigh from redbot.core.utils.chat_formatting import box, humanize_list, pagify from .phrases import FRIENDS, SNACKBURR_PHRASES log = logging.getLogger("red.aikaterna.snacktime") class Snacktime(commands.Cog): """Snackburr's passing out pb jars!""" async def red_delete_data_for_user(self, **kwargs): """ Nothing to delete """ return def __init__(self, bot): self.bot = bot self.config = Config.get_conf(self, 2712291001, force_registration=True) self.snackSchedule = {} self.snacktimePrediction = {} self.previousSpeaker = {} self.snackInProgress = {} self.acceptInput = {} self.alreadySnacked = {} self.msgsPassed = {} self.startLock = {} self.snacktimeCheckLock = {} self.lockRequests = {} self.channel_persona = {} default_guild = { "DELIVER_CHANNELS": [], "FRIENDS": False, "EVENT_START_DELAY": 1800, "EVENT_START_DELAY_VARIANCE": 900, "SNACK_DURATION": 240, "SNACK_DURATION_VARIANCE": 120, "MSGS_BEFORE_EVENT": 8, "SNACK_AMOUNT": 200, "USE_CURRENCY": False } default_channel = {"repeatMissedSnacktimes": 0} self.config.register_guild(**default_guild) self.config.register_channel(**default_channel) async def persona_choice(self, ctx: None, message: None): if ctx: invite_friends = await self.config.guild(ctx.guild).FRIENDS() else: invite_friends = await self.config.guild(message.guild).FRIENDS() personas = FRIENDS if not invite_friends: return "Snackburr" if message else "ʕ •ᴥ•ʔ <" elif invite_friends is True: try: del personas["Snackburr"] except KeyError: pass if message: return randchoice(list(personas.keys())) else: return randchoice(list(personas.values())) async def get_response(self, msg, phrase_type): scid = f"{msg.guild.id}-{msg.channel.id}" persona = self.channel_persona[scid] persona_phrase = FRIENDS.get(persona) phrase = randchoice(SNACKBURR_PHRASES[phrase_type]) return f"`{persona_phrase} {phrase}`" # Is snackburr gonna like the currency or nah @staticmethod def is_custom(currency_name: str): custom = re.search(r'<:\w*:\d*>', currency_name) animated = re.search(r'', currency_name) if custom or animated: return True @commands.cooldown(1, 1, commands.BucketType.channel) @commands.guild_only() @commands.command() async def eat(self, ctx, amount: int): """ all this talk about pb is makin me hungry. how bout you guys? """ use_red_currency = await self.config.guild(ctx.guild).USE_CURRENCY() if use_red_currency: currency_name = await bank.get_currency_name(ctx.guild) else: currency_name = "pb jars" if self.is_custom(currency_name): currency_name = f"`{currency_name}`" persona = await self.persona_choice(ctx=ctx, message=None) if amount < 0: return await ctx.send(f"`{persona} Woah slow down!`") if amount > await bank.get_balance(ctx.author): return await ctx.send(f"`{persona} You don't got that much {currency_name}!.. don't look at me..`") await bank.withdraw_credits(ctx.author, amount) first_phrase = randchoice(SNACKBURR_PHRASES["EAT_BEFORE"]) second_phrase = randchoice(SNACKBURR_PHRASES["EAT_AFTER"]) await ctx.send( f"`{persona} {ctx.author.display_name} {first_phrase} {second_phrase} {amount} whole {currency_name}!`" ) @commands.guild_only() @commands.group() @checks.mod_or_permissions(manage_guild=True) async def snackset(self, ctx): """snack stuff""" if ctx.invoked_subcommand is None: guild_data = await self.config.guild(ctx.guild).all() channel_names = [] if guild_data["DELIVER_CHANNELS"]: for channel_id in guild_data["DELIVER_CHANNELS"]: channel_obj = self.bot.get_channel(channel_id) if channel_obj: channel_names.append(channel_obj.name) if len(channel_names) == 0: channel_names = ["No channels set."] if guild_data["FRIENDS"] is True: invite_friends = "Friends only" elif guild_data["FRIENDS"] is False: invite_friends = "Snackburr only" else: invite_friends = "Everyone's invited!" use_red_currency = await self.config.guild(ctx.guild).USE_CURRENCY() msg = f"[Delivering in]: {humanize_list(channel_names)}\n" msg += f"[Event start delay]: {guild_data['EVENT_START_DELAY']} seconds\n" msg += f"[Event start variance]: {guild_data['EVENT_START_DELAY_VARIANCE']} seconds\n" msg += f"[Friends status]: {invite_friends}\n" msg += f"[Messages before event]: {guild_data['MSGS_BEFORE_EVENT']}\n" msg += f"[Snack amount limit]: {guild_data['SNACK_AMOUNT']}\n" msg += f"[Use custom currency]: {use_red_currency}\n" msg += f"[Snack duration]: {guild_data['SNACK_DURATION']} seconds\n" msg += f"[Snack duration variance]: {guild_data['SNACK_DURATION_VARIANCE']} seconds\n" for page in pagify(msg, delims=["\n"]): await ctx.send(box(page, lang="ini")) @snackset.command() async def errandtime(self, ctx, seconds: int): """How long snackburr needs to be out doin errands.. more or less.""" event_start_delay_variance = await self.config.guild(ctx.guild).EVENT_START_DELAY_VARIANCE() if seconds <= event_start_delay_variance: await ctx.send("errandtime must be greater than errandvariance!") elif seconds <= 0: await ctx.send("errandtime must be greater than 0") else: await self.config.guild(ctx.guild).EVENT_START_DELAY.set(seconds) await ctx.send(f"snackburr's errands will now take around {round(seconds/60, 2)} minutes!") @snackset.command() async def errandvariance(self, ctx, seconds: int): """How early or late snackburr might be to snacktime""" event_start_delay = await self.config.guild(ctx.guild).EVENT_START_DELAY() if seconds >= event_start_delay: await ctx.send("errandvariance must be less than errandtime!") elif seconds < 0: await ctx.send("errandvariance must be 0 or greater!") else: await self.config.guild(ctx.guild).EVENT_START_DELAY_VARIANCE.set(seconds) await ctx.send(f"snackburr now might be {round(seconds/60, 2)} minutes early or late to snacktime") @snackset.command(name="snacktime") async def snacktimetime(self, ctx, seconds: int): """How long snackburr will hang out giving out snacks!.. more or less.""" snack_duration_variance = await self.config.guild(ctx.guild).SNACK_DURATION_VARIANCE() if seconds <= snack_duration_variance: await ctx.send("snacktime must be greater than snackvariance!") elif seconds <= 0: await ctx.send("snacktime must be greater than 0") else: await self.config.guild(ctx.guild).SNACK_DURATION.set(seconds) await ctx.send(f"snacktimes will now last around {round(seconds/60, 2)} minutes!") @snackset.command(name="snackvariance") async def snacktimevariance(self, ctx, seconds: int): """How early or late snackburr might have to leave for errands""" snack_duration = await self.config.guild(ctx.guild).SNACK_DURATION() if seconds >= snack_duration: await ctx.send("snackvariance must be less than snacktime!") elif seconds < 0: await ctx.send("snackvariance must be 0 or greater!") else: await self.config.guild(ctx.guild).SNACK_DURATION_VARIANCE.set(seconds) await ctx.send(f"snackburr now may have to leave snacktime {round(seconds/60, 2)} minutes early or late") @snackset.command() async def msgsneeded(self, ctx, amt: int): """How many messages must pass in a conversation before a snacktime can start""" if amt <= 0: await ctx.send("msgsneeded must be greater than 0") else: await self.config.guild(ctx.guild).MSGS_BEFORE_EVENT.set(amt) await ctx.send(f"snackburr will now wait until {amt} messages pass until he comes with snacks") @snackset.command() async def amount(self, ctx, amt: int): """How much pb max snackburr should give out to each person per snacktime""" if amt <= 0: await ctx.send("amount must be greater than 0") else: await self.config.guild(ctx.guild).SNACK_AMOUNT.set(amt) use_red_currency = await self.config.guild(ctx.guild).USE_CURRENCY() if use_red_currency: currency_name = await bank.get_currency_name(ctx.guild) else: currency_name = "pb jars" if self.is_custom(currency_name): currency_name = f"`{currency_name}`" await ctx.send(f"snackburr will now give out {amt} {currency_name} max per person per snacktime.") @snackset.command() async def togglecurrency(self, ctx): """Toggle whether to use server currency name instead of pb""" toggled = await self.config.guild(ctx.guild).USE_CURRENCY() if not toggled: currency_name = await bank.get_currency_name(ctx.guild) if self.is_custom(currency_name): await ctx.send("snackburr doesnt like that currency name.. but will use it anyway :unamused:") else: await ctx.send("snackburr will now use the bots currency name... lame.....") await self.config.guild(ctx.guild).USE_CURRENCY.set(True) else: await self.config.guild(ctx.guild).USE_CURRENCY.set(False) await ctx.send("snackburr will now use pb again yay!") @snackset.command(name="friends") async def snackset_friends(self, ctx, choice: int): """snackburr's friends wanna know what all the hub-bub's about! Do you want to 1: invite them to the party, 2: only allow snackburr to chillax with you guys, or 3: kick snackburr out on the curb in favor of his obviously cooler friends? """ if choice not in (1, 2, 3): return await ctx.send_help() choices = { 1: ("both", "Everybody's invited!"), 2: (False, "You chose to not invite snackburr's friends."), 3: (True, "You kick snackburr out in favor of his friends! Ouch. Harsh..."), } choice = choices[choice] await self.config.guild(ctx.guild).FRIENDS.set(choice[0]) await ctx.send(choice[1]) @snackset.command() async def deliver(self, ctx): """Asks snackburr to start delivering to this channel""" deliver_channels = await self.config.guild(ctx.guild).DELIVER_CHANNELS() if not deliver_channels: deliver_channels = [] if ctx.channel.id not in deliver_channels: deliver_channels.append(ctx.channel.id) await self.config.guild(ctx.guild).DELIVER_CHANNELS.set(deliver_channels) await ctx.send("snackburr will start delivering here!") else: deliver_channels.remove(ctx.channel.id) await self.config.guild(ctx.guild).DELIVER_CHANNELS.set(deliver_channels) await ctx.send("snackburr will stop delivering here!") @commands.guild_only() @commands.command() async def snacktime(self, ctx): """Man i'm hungry! When's snackburr gonna get back with more snacks?""" scid = f"{ctx.message.guild.id}-{ctx.message.channel.id}" if self.snacktimePrediction.get(scid, None) == None: if self.acceptInput.get(scid, False): return else: phrases = [ r"Don't look at me. I donno where snackburr's at ¯\_(ツ)_/¯", "I hear snackburr likes parties. *wink wink", "I hear snackburr is attracted to channels with active conversations", "If you party, snackburr will come! 〈( ^o^)ノ", ] await ctx.send(randchoice(phrases)) return seconds = self.snacktimePrediction[scid] - self.bot.loop.time() if self.snacktimeCheckLock.get(scid, False): if randint(1, 4) == 4: await ctx.send("Hey, snackburr's on errands. I ain't his keeper Kappa") return self.snacktimeCheckLock[scid] = True if seconds < 0: await ctx.send(f"I'm not sure where snackburr is.. He's already {round(abs(seconds/60), 2)} minutes late!") else: await ctx.send(f"snackburr's out on errands! I think he'll be back in {round(seconds/60, 2)} minutes") await asyncio.sleep(40) self.snacktimeCheckLock[scid] = False async def startSnack(self, message): scid = f"{message.guild.id}-{message.channel.id}" if self.acceptInput.get(scid, False): return self.channel_persona[scid] = await self.persona_choice(ctx=None, message=message) await message.channel.send(await self.get_response(message, "SNACKTIME")) self.acceptInput[scid] = True self.alreadySnacked[scid] = [] guild_data = await self.config.guild(message.guild).all() duration = guild_data["SNACK_DURATION"] + randint( -guild_data["SNACK_DURATION_VARIANCE"], guild_data["SNACK_DURATION_VARIANCE"] ) await asyncio.sleep(duration) # sometimes fails sending messages and stops all future snacktimes. Hopefully this fixes it. try: # list isn't empty if self.alreadySnacked.get(scid, False): await message.channel.send(await self.get_response(message, "OUT")) await self.config.channel(message.channel).repeatMissedSnacktimes.set(0) else: await message.channel.send(await self.get_response(message, "NO_TAKERS")) repeat_missed_snacktimes = await self.config.channel(message.channel).repeatMissedSnacktimes() await self.config.channel(message.channel).repeatMissedSnacktimes.set(repeat_missed_snacktimes + 1) await asyncio.sleep(2) if (repeat_missed_snacktimes + 1) > 9: # move to a setting await message.channel.send(await self.get_response(message, "LONELY")) deliver_channels = await self.config.guild(message.guild).DELIVER_CHANNELS() new_deliver_channels = deliver_channels.remove(message.channel.id) await self.config.guild(message.guild).DELIVER_CHANNELS.set(new_deliver_channels) await self.config.channel(message.channel).repeatMissedSnacktimes.set(0) except: log.error("Snacktime: Failed to send message in startSnack") self.acceptInput[scid] = False self.snackInProgress[scid] = False @commands.Cog.listener() async def on_message(self, message): if not message.guild: return if message.author.bot: return if not message.channel.permissions_for(message.guild.me).send_messages: return deliver_channels = await self.config.guild(message.guild).DELIVER_CHANNELS() if not deliver_channels: return if message.channel.id not in deliver_channels: return scid = f"{message.guild.id}-{message.channel.id}" if message.author.id != self.bot.user.id: # if nobody has said anything since start if self.previousSpeaker.get(scid, None) == None: self.previousSpeaker[scid] = message.author.id # if new speaker elif self.previousSpeaker[scid] != message.author.id: self.previousSpeaker[scid] = message.author.id msgTime = self.bot.loop.time() # if there's a scheduled snack if self.snackSchedule.get(scid, None) != None: # if it's time for a snack if msgTime > self.snackSchedule[scid]: # 1 schedule at a time, so remove schedule self.snackSchedule[scid] = None self.snackInProgress[scid] = True # wait to make it more natural naturalWait = randint(30, 240) log.debug(f"Snacktime: snack trigger msg: {message.content}") log.debug(f"Snacktime: Waiting {str(naturalWait)} seconds") await asyncio.sleep(naturalWait) # start snacktime await self.startSnack(message) # if no snack coming, schedule one elif self.snackInProgress.get(scid, False) == False and not self.startLock.get(scid, False): self.msgsPassed[scid] = self.msgsPassed.get(scid, 0) + 1 # check for collisions msgs_before_event = await self.config.guild(message.guild).MSGS_BEFORE_EVENT() if self.msgsPassed[scid] > msgs_before_event: self.startLock[scid] = True if self.lockRequests.get(scid, None) == None: self.lockRequests[scid] = [] self.lockRequests[scid].append(message) await asyncio.sleep(1) log.debug( f"Snacktime: :-+-|||||-+-: Lock request: {str(self.lockRequests[scid][0] == message)}" ) if self.lockRequests[scid][0] == message: await asyncio.sleep(5) log.debug(f"Snacktime: {message.author.name} - I got the Lock") self.lockRequests[scid] = [] # someone got through already if self.msgsPassed[scid] < msgs_before_event or self.snackInProgress.get(scid, False): log.debug("Snacktime: Lock: someone got through already.") return else: log.debug( "Snacktime: Lock: looks like i'm in the clear. lifting lock. If someone comes now, they should get the lock" ) self.msgsPassed[scid] = msgs_before_event self.startLock[scid] = False else: log.debug(f"Snacktime: {message.author.name} Failed lock") return if self.msgsPassed[scid] == msgs_before_event: # schedule a snack log.debug(f"Snacktime: activity: {message.content}") guild_data = await self.config.guild(message.guild).all() timeTillSnack = guild_data["EVENT_START_DELAY"] + randint( -guild_data["EVENT_START_DELAY_VARIANCE"], guild_data["EVENT_START_DELAY_VARIANCE"], ) log.debug(f"Snacktime: {str(timeTillSnack)} seconds till snacktime") self.snacktimePrediction[scid] = msgTime + guild_data["EVENT_START_DELAY"] self.snackSchedule[scid] = msgTime + timeTillSnack self.msgsPassed[scid] = 0 # it's snacktime! who want's snacks? if self.acceptInput.get(scid, False): if message.author.id not in self.alreadySnacked.get(scid, []): agree_phrases = [ "holds out hand", "im ready", "i'm ready", "hit me up", "hand over", "hand me", "kindly", "i want", "i'll have", "ill have", "yes", "pls", "plz", "please", "por favor", "can i", "i'd like", "i would", "may i", "in my mouth", "in my belly", "snack me", "gimme", "give me", "i'll take", "ill take", "i am", "about me", "me too", "of course", ] userWants = False for agreePhrase in agree_phrases: # no one word answers if agreePhrase in message.content.lower() and len(message.content.split()) > 1: userWants = True break if userWants: if self.alreadySnacked.get(scid, None) == None: self.alreadySnacked[scid] = [] self.alreadySnacked[scid].append(message.author.id) # If user is blacklisted, don't give him/her anything. # We're still passing it to the list to avoid this calculation down the line, if await self.bot.allowed_by_whitelist_blacklist( who=message.author ) is False: return await asyncio.sleep(randint(1, 6)) snack_amount = await self.config.guild(message.guild).SNACK_AMOUNT() snackAmt = randint(1, snack_amount) try: if self.acceptInput.get(scid, False): resp = await self.get_response(message, "GIVE") resp = resp.format(message.author.name, snackAmt) use_red_currency = await self.config.guild(message.guild).USE_CURRENCY() if use_red_currency: currency_name = await bank.get_currency_name(message.guild) if self.is_custom(currency_name): currency_name = f"`{currency_name}`" resp = resp.replace("pb", currency_name) await message.channel.send(resp) else: resp = await self.get_response(message, "LAST_SECOND") resp = resp.format(message.author.name, snackAmt) use_red_currency = await self.config.guild(message.guild).USE_CURRENCY() if use_red_currency: currency_name = await bank.get_currency_name(message.guild) if self.is_custom(currency_name): currency_name = f"`{currency_name}`" resp = resp.replace("pb", currency_name) await message.channel.send(resp) try: await bank.deposit_credits(message.author, snackAmt) except BalanceTooHigh as b: await bank.set_balance(message.author, b.max_balance) except Exception as e: log.info( f"Failed to send pb message. {message.author.name} didn't get pb\n", exc_info=True, ) else: more_phrases = [ "more pl", "i have some more", "i want more", "i have another", "i have more", "more snack", ] userWants = False for morePhrase in more_phrases: if morePhrase in message.content.lower(): userWants = True break if userWants: if await self.bot.allowed_by_whitelist_blacklist( who=message.author ) is False: return await asyncio.sleep(randint(1, 6)) if self.acceptInput.get(scid, False): resp = await self.get_response(message, "GREEDY") await message.channel.send(resp.format(message.author.name))