Extensible chat app with command bots
Implement an in-memory chat app with one shared channel that people and bots post to. Users send text. Some messages are slash commands (/name arg ...), and bots can respond to those. Bots can also react to ordinary messages.
The main goal is extensibility. Adding a new bot type must not require changes to ChatApp.
API
class ChatApp:
def __init__(self, bots: list[Bot] | None = None) -> None: ...
def add_bot(self, bot: Bot) -> None: ...
def send_message(self, user: str, text: str) -> list[str]: ...
def get_log(self) -> list[str]: ...
- Log entries are rendered as
"sender: text". Bot replies use the bot's name as the sender. -
send_messagefirst appends the user's message to the log. Then bots may append replies, in the order the bots were registered. It returns the lines this call added. -
get_logreturns the whole channel history in order. - Bot replies never trigger other bots.
- A command that no bot owns is logged with no reply. Command names are case-insensitive.
- If two bots register the same command,
add_botraisesValueError.
Bots to support
AwayBot
-
/away [reason]marks the sender away and replies"<user> is away: <reason>". The default reason isaway. -
/backreplies"welcome back, <user>", or"<user> was not away"if the sender was not away. - When a plain (non-command) message mentions
@nameand that user is away, it replies"<name> is away: <reason>". It posts this once per away user, in the order they were mentioned, and never about the sender.
MeetBot
-
/meet @a @b ...replies"meeting #<n> started: <sender>, a, b". Numbers start at 1. The sender comes first, duplicates are removed, and the rest keep the order they were mentioned. - With no mentions, it replies
"usage: /meet @user [@user ...]"and does not use up a meeting number.
TacoBot
-
/taco @user [n]givesntacos (default 1, allowed range 1-5) and replies"<giver> gave <n> taco(s) to <user> (<user> now has <total>)". - An invalid amount or a missing target gets
"usage: /taco @user [1-5]". Giving tacos to yourself gets"you can't give tacos to yourself". -
/tacosreplies"tacos: u1=n1, u2=n2", sorted by count descending and then by name. If nobody has tacos yet, it replies"no tacos yet".
Example
app = ChatApp([AwayBot(), MeetBot(), TacoBot()])
app.send_message("dana", "/away dentist")
# ["dana: /away dentist", "AwayBot: dana is away: dentist"]
app.send_message("eli", "@dana can you review?")
# ["eli: @dana can you review?", "AwayBot: dana is away: dentist"]
app.send_message("eli", "/taco @fay 2")
# ["eli: /taco @fay 2", "TacoBot: eli gave 2 taco(s) to fay (fay now has 2)"]
app.send_message("fay", "/meet @eli @eli")
# ["fay: /meet @eli @eli", "MeetBot: meeting #1 started: fay, eli"]
Be ready to explain how a fourth bot would be added, and how the design would change for multiple channels.
Reconstructed from the reported question; details may differ from what interviewers ask.
Sign in to see the worked answer
The full answer, chapter by chapter, with what interviewers expect at each level. Free with an account: one full answer per company.
