Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,13 @@ You'll need your own throwaway Discord server and bot to develop against:
1. In the [Discord Developer Portal](https://discord.com/developers/applications),
create an application, then under **Bot** click **Reset Token** and copy the
token. No privileged intents are required — `/ask` uses default intents.
2. Under **OAuth2 → URL Generator**, select the `bot` and `applications.commands`
2. Scroll down and check the permissions the bot needs: **Send Messages**,
**View Channel** and **Read Message History**.
3. Under **OAuth2 → URL Generator**, select the `bot` and `applications.commands`
scopes, open the generated URL, and invite the bot to a server you own.
3. Enable **Settings → Advanced → Developer Mode**, then right-click your server
4. Enable **Settings → Advanced → Developer Mode**, then right-click your server
icon → **Copy Server ID**.
4. Add both values to your `.env`:
5. Add both values to your `.env`:

```
DISCORD_BOT_TOKEN=your_token_here
Expand Down
88 changes: 85 additions & 3 deletions src/apps/discord_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,38 @@ async def on_ready(self) -> None:
await self.tree.sync(guild=guild)
log.info("discord_ready", self_user=self.user, guild=settings.discord_guild_id)

async def on_message(self, message) -> None:
# runs only in servers
if message.guild is None:
return

# ignores other bots and itself
if message.author.bot:
return

bot_standard_mention = f"<@{self.user.id}>"
bot_nickname_mention = f"<@!{self.user.id}>"

# triggering on explicit mention
if bot_standard_mention in message.content or bot_nickname_mention in message.content:
question = await question_assembler(message)

# if question is just empty/non-text
if question == "":
return

async with message.channel.typing():
answer = await ask(question=question)

content = await render_answer_content(answer)

try:
await message.reply(content)
except Exception as e:
await message.reply("❌ I couldn't process this request, please try again later.")
log.error("discord_ask_failed", error=str(e))
return


client = CsAssistantBot()

Expand All @@ -41,12 +73,62 @@ async def ask_command(interaction: discord.Interaction, question: str) -> None:
log.error("discord_ask_failed", interaction_id=interaction.id, error=str(e))
return

content = await render_answer_content(answer)
await interaction.followup.send(content)
log.info("discord_ask_completed", interaction_id=interaction.id)


async def question_assembler(message) -> str:
# if message is reply to a question
referenced_text = ""
if message.reference and message.reference.message_id:
try:
referenced_message = await message.channel.fetch_message(message.reference.message_id)
referenced_text = referenced_message.content
except discord.NotFound:
log.info(
"No referenced text. Not Found.",
channel_id=message.channel.id,
channel_name=message.channel.name,
guild_id=message.guild.id,
target_message_id=message.reference.message_id,
)
except discord.Forbidden:
log.warning(
"No referenced text. Forbidden.",
channel_id=message.channel.id,
channel_name=message.channel.name,
guild_id=message.guild.id,
target_message_id=message.reference.message_id,
)

return question_assembler_helper(
content=message.content,
bot_id=message.guild.me.id,
referenced_text=referenced_text,
)


def question_assembler_helper(*, content: str, bot_id: int, referenced_text: str = "") -> str:
mention_text = strip_bot_mention(content, bot_id)

if referenced_text:
return referenced_text + "\n\n" + mention_text
return mention_text


def strip_bot_mention(content: str, bot_id: int) -> str:
mention_text = content
for x in {f"<@{bot_id}>", f"<@!{bot_id}>"}:
mention_text = mention_text.replace(x, "")
return mention_text.strip()


async def render_answer_content(answer) -> str:
content = answer.text
if answer.sources:
content += "\n\n**Sources:**\n" + "\n".join(f"• {source.url}" for source in answer.sources)

await interaction.followup.send(content)
log.info("discord_ask_completed", interaction_id=interaction.id)
return content


if __name__ == "__main__":
Expand Down
27 changes: 27 additions & 0 deletions tests/apps/test_discord_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from src.apps.discord_bot import question_assembler_helper, strip_bot_mention

BOT_ID = 123456789


def test_plain_mention():
message = f"<@{BOT_ID}> test question"
assert (strip_bot_mention(message, BOT_ID)) == "test question"


def test_both_mentions():
message = f"<@{BOT_ID}> <@!{BOT_ID}> test question"
assert (strip_bot_mention(message, BOT_ID)) == "test question"


def test_concatenate_reference_and_original():
message = f"<@{BOT_ID}> test question"
referenced_text = "Original text"
assert (
question_assembler_helper(content=message, bot_id=BOT_ID, referenced_text=referenced_text)
== "Original text\n\ntest question"
)


def test_empty_response():
message = f"<@{BOT_ID}>"
assert (question_assembler_helper(content=message, bot_id=BOT_ID)) == ""
Loading