diff --git a/snacktime/__init__.py b/snacktime/__init__.py new file mode 100755 index 0000000..a0372b1 --- /dev/null +++ b/snacktime/__init__.py @@ -0,0 +1,5 @@ +from .snacktime import Snacktime + + +async def setup(bot): + bot.add_cog(Snacktime(bot)) diff --git a/snacktime/info.json b/snacktime/info.json new file mode 100644 index 0000000..5ca4a2d --- /dev/null +++ b/snacktime/info.json @@ -0,0 +1,9 @@ +{ + "NAME" : "Snacktime", + "AUTHOR" : ["irdumb", "aikaterna"], + "SHORT" : "ʕ •ᴥ•ʔ < It's snacktime, who wants snacks?", + "DESCRIPTION" : "snackburr will come around every-so-often if you've asked him to.\nI hear snackburr likes to come around more often when people are partyin.", + "INSTALL_MSG" : "A snack delivery bear has ʕ•ᴥ• ʔ", + "TAGS" : ["snack", "snacktime", "snackburr", "party", "party time"], + "DISABLED" : false +} diff --git a/snacktime/phrases.py b/snacktime/phrases.py new file mode 100644 index 0000000..1244a8f --- /dev/null +++ b/snacktime/phrases.py @@ -0,0 +1,73 @@ +FRIENDS = { + "Snackburr": "ʕ •ᴥ•ʔ <", + "Pancakes": "₍⸍⸌̣ʷ̣̫⸍̣⸌₎ <", + "Mr Pickles": "(=`ェ´=) <", + "Satin": "▼・ᴥ・▼ <", + "Thunky": "ᘛ⁐̤ᕐᐷ <", + "Jingle": "꒰∗´꒳`꒱ <", + "FluffButt": "/ᐠ。ꞈ。ᐟ\ <", + "Staplefoot": "( ̄(エ) ̄) <", +} + + +SNACKBURR_PHRASES = { + "SNACKTIME": [ + "It's snack time!", + "I'm back with s'more snacks! Who wants!?", + "I'm back errbody! Who wants some snacks!?", + "Woo man those errands are crazy! Anyways, anybody want some snacks?", + "I got snacks! If nobody wants em, I'm gonna eat em all!!", + "Hey, I'm back! Anybody in the mood for some snacks?!", + "Heyyaaayayyyaya! I say Hey, I got snacks!", + "Heyyaaayayyyaya! I say Hey, What's goin on?... I uh.. I got snacks.", + "If anybody has reason why these snacks and my belly should not be wed, speak now or forever hold your peace!", + "Got another snack delivery guys!", + "Did somebody say snacks?!?! o/", + "Choo Choo! it's the pb train! Come on over guys!", + "Snacks are here! Dig in! Who wants a plate?", + "Pstt.. I got the snacks you were lookin for. <.<", + "I hope you guys are hungry! Cause i'm loaded to the brim with snacks!!!", + "I was hungry on the way over so I kinda started without you guys :3 Who wants snacks!?!", + "Beep beep! I got a snack delivery comin in! Who wants snacks!", + "Guess what time it is?! It's snacktime!! Who wants?!", + "Hey check out this sweet stach o' snacks I found! Who wants a cut?", + "Who's ready to gobble down some snacks!?", + "So who's gonna help me eat all these snacks? :3", + ], + "OUT": [ + "I'm out of snacks! I'll be back with more soon.", + "I'm out of snacks :( I'll be back soon with more!", + "Aight, I gotta head out! I'll be back with more, don worry :3", + "Alright, I gotta get back to my errands. I'll see you guys soon!", + ], + "LONELY": ["I guess you guys don't like snacktimes.. I'll stop comin around."], + "NO_TAKERS": [ + "I guess nobody wants snacks... more for me!", + "Guess nobody's here.. I'll just head out then", + "I don't see anybody.. <.< ... >.> ... All the snacks for me!!", + "I guess nobody wants snacks huh.. Well, I'll come back later", + "I guess i'll just come back later..", + ], + "GIVE": [ + "Here ya go, {0}, here's {1} pb!", + "Alright here ya go, {0}, {1} pb for you!", + "Yeah! Here you go, {0}! {1} pb!", + "Of course {0}! Here's {1} pb!", + "Ok {0}, here's {1} pb for you. Anyone else want some?", + "Alllright, {1} pb for {0}!", + "Hold your horses {0}! Alright, {1} pb for you :)", + ], + "LAST_SECOND": [ + "Fine fine, {0}, I'll give you {1} of my on-the-road pb.. Cya!", + "Oh! {0}, you caught me right before I left! Alright, i'll give you {1} of my own pb", + ], + "GREEDY": [ + "Don't be greedy now! you already got some pb {0}!", + "You already got your snacks {0}!", + "Come on {0}, you already got your snacks! We gotta make sure there's some for errbody!", + ], + "ENABLE": [ + "Oh you guys want snacks?! Aight, I'll come around every so often to hand some out!" + ], + "DISABLE": ["You guys don't want snacks anymore? Alright, I'll stop comin around."], +} diff --git a/snacktime/snacktime.py b/snacktime/snacktime.py new file mode 100644 index 0000000..2b0cba4 --- /dev/null +++ b/snacktime/snacktime.py @@ -0,0 +1,468 @@ +import asyncio +import discord +import logging +from random import randint +from random import choice as randchoice + +from redbot.core import bank, checks, commands, Config +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!""" + + 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, + } + + default_channel = {"repeatMissedSnacktimes": 0} + + self.config.register_guild(**default_guild) + self.config.register_channel(**default_channel) + + async def persona_choice(self, msg): + invite_friends = await self.config.guild(msg.guild).FRIENDS() + personas = FRIENDS + if not invite_friends: + return "Snackburr" + elif invite_friends is True: + del personas["Snackburr"] + return randchoice(list(personas.keys())) + + 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}`" + + @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() + if not guild_data["DELIVER_CHANNELS"]: + channel_names = ["No channels set."] + else: + channel_names = [] + for channel_id in guild_data["DELIVER_CHANNELS"]: + channel_obj = self.bot.get_channel(channel_id) + channel_names.append(channel_obj.name) + + 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!" + + 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']} pb\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) + await ctx.send(f"snackburr will now give out {amt} pb max per person per snacktime.") + + @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.command(pass_context=True) + 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 = [ + "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 {seconds / 60} minutes late!" + ) + else: + await ctx.send( + f"snackburr's out on errands! I think he'll be back in {seconds / 60} 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(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) + 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) + await message.channel.send(resp) + else: + resp = await self.get_response(message, "LAST_SECOND") + resp = resp.format(message.author.name, snackAmt) + await message.channel.send(resp) + await bank.deposit_credits(message.author, snackAmt) + except: + log.info( + f"Snacktime: Failed to send pb message. {message.author.name} didn't get pb" + ) + + 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: + await asyncio.sleep(randint(1, 6)) + if self.acceptInput.get(scid, False): + await message.channel.send( + await self.get_response(message, "GREEDY").format( + message.author.name + ) + )