1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
import json
from pathlib import Path
from random import choice
from typing import TypedDict
import discord
from discord.ext import commands
from bot.bot import Bot
from bot.constants import Colours, NEGATIVE_REPLIES
TIMEOUT = 60.0
class MadlibsTemplate(TypedDict):
"""Structure of a template in the madlibs JSON file."""
title: str
blanks: list[str]
value: list[str]
class Madlibs(commands.Cog):
"""Cog for the Madlibs game."""
def __init__(self, bot: Bot):
self.bot = bot
self.templates = self._load_templates()
self.edited_content = {}
self.checks = set()
@staticmethod
def _load_templates() -> list[MadlibsTemplate]:
madlibs_stories = Path("bot/resources/fun/madlibs_templates.json")
with open(madlibs_stories) as file:
return json.load(file)
@staticmethod
def madlibs_embed(part_of_speech: str, number_of_inputs: int) -> discord.Embed:
"""Method to generate an embed with the game information."""
madlibs_embed = discord.Embed(title="Madlibs", color=Colours.python_blue)
madlibs_embed.add_field(
name="Enter a word that fits the given part of speech!",
value=f"Part of speech: {part_of_speech}\n\nMake sure not to spam, or you may get auto-muted!"
)
madlibs_embed.set_footer(text=f"Inputs remaining: {number_of_inputs}")
return madlibs_embed
@commands.Cog.listener()
async def on_message_edit(self, _: discord.Message, after: discord.Message) -> None:
"""A listener that checks for message edits from the user."""
for check in self.checks:
if check(after):
break
else:
return
self.edited_content[after.id] = after.content
@commands.command()
@commands.max_concurrency(1, per=commands.BucketType.user)
async def madlibs(self, ctx: commands.Context) -> None:
"""
Play Madlibs with the bot!
Madlibs is a game where the player is asked to enter a word that
fits a random part of speech (e.g. noun, adjective, verb, plural noun, etc.)
a random amount of times, depending on the story chosen by the bot at the beginning.
"""
random_template = choice(self.templates)
def author_check(message: discord.Message) -> bool:
return message.channel.id == ctx.channel.id and message.author.id == ctx.author.id
self.checks.add(author_check)
loading_embed = discord.Embed(
title="Madlibs", description="Loading your Madlibs game...", color=Colours.python_blue
)
original_message = await ctx.send(embed=loading_embed)
submitted_words = {}
for i, part_of_speech in enumerate(random_template["blanks"]):
inputs_left = len(random_template["blanks"]) - i
madlibs_embed = self.madlibs_embed(part_of_speech, inputs_left)
await original_message.edit(embed=madlibs_embed)
try:
message = await self.bot.wait_for("message", check=author_check, timeout=TIMEOUT)
except TimeoutError:
timeout_embed = discord.Embed(
title=choice(NEGATIVE_REPLIES),
description="Uh oh! You took too long to respond!",
color=Colours.soft_red
)
await ctx.send(ctx.author.mention, embed=timeout_embed)
for msg_id in submitted_words:
self.edited_content.pop(msg_id, submitted_words[msg_id])
self.checks.remove(author_check)
return
submitted_words[message.id] = message.content
blanks = [self.edited_content.pop(msg_id, submitted_words[msg_id]) for msg_id in submitted_words]
self.checks.remove(author_check)
story = []
for value, blank in zip(random_template["value"], blanks, strict=True):
story.append(f"{value}__{blank}__")
# In each story template, there is always one more "value"
# (fragment from the story) than there are blanks (words that the player enters)
# so we need to compensate by appending the last line of the story again.
story.append(random_template["value"][-1])
story_embed = discord.Embed(
title=random_template["title"],
description="".join(story),
color=Colours.bright_green
)
story_embed.set_footer(text=f"Generated for {ctx.author}", icon_url=ctx.author.display_avatar.url)
await ctx.send(embed=story_embed)
@madlibs.error
async def handle_madlibs_error(self, ctx: commands.Context, error: commands.CommandError) -> None:
"""Error handler for the Madlibs command."""
if isinstance(error, commands.MaxConcurrencyReached):
await ctx.send("You are already playing Madlibs!")
error.handled = True
async def setup(bot: Bot) -> None:
"""Load the Madlibs cog."""
await bot.add_cog(Madlibs(bot))
|