diff --git a/docs/examples/discord_bot.mdx b/docs/examples/discord_bot.mdx index 66446a40..9c9f3b4b 100644 --- a/docs/examples/discord_bot.mdx +++ b/docs/examples/discord_bot.mdx @@ -8,40 +8,50 @@ title: '🤖 Discord Bot' - Go to [https://discord.com/developers/applications/](https://discord.com/developers/applications/) and click on `New Application`. - Enter the name for your bot, accept the terms and click on `Create`. On the resulting page, enter the details of your bot as you like. - On the left sidebar, click on `Bot`. Under the heading `Privileged Gateway Intents`, toggle all 3 options to ON position. Save your changes. -- Now click on `Reset Token` and copy the token value. Set it as `DISCORD_BOT_TOKEN` in variables.env file. +- Now click on `Reset Token` and copy the token value. Set it as `DISCORD_BOT_TOKEN` in .env file. - On the left sidebar, click on `OAuth2` and go to `General`. - Set `Authorization Method` to `In-app Authorization`. Under `Scopes` select `bot`. - Under `Bot Permissions` allow the following and then click on `Save Changes`. ```text -Read Messages/View Channel (under General Permissions) Send Messages (under Text Permissions) -Read Message History (under Text Permissions) -Mention everyone (under Text Permissions) ``` - Now under `OAuth2` and go to `URL Generator`. Under `Scopes` select `bot`. - Under `Bot Permissions` set the same permissions as above. - Now scroll down and copy the `Generated URL`. Paste it in a browser window and select the Server where you want to add the bot. - Click on `Continue` and authorize the bot. -- 🎉 The bot has been successfully added to your server. +- 🎉 The bot has been successfully added to your server. But it's still offline. -### 🐳 Docker Setup +### Take the bot online + +1. Install embedchain python package: -- To setup your discord bot using docker, run the following command inside this folder using your terminal. ```bash -docker-compose up --build +pip install "embedchain[discord]" +``` + +2. Launch your Discord bot: + + +```bash +python -m embedchain.bots.discord +``` + +If you prefer to see the question and not only the answer, run it with + +```bash +python -m embedchain.bots.discord --include-question ``` -📝 Note: The build command might take a while to install all the packages depending on your system resources. ### 🚀 Usage Instructions - Go to the server where you have added your bot. -- You can add data sources to the bot using the command: +- You can add data sources to the bot using the slash command: ```text -/ec add +/add ``` -- You can ask your queries from the bot using the command: +- You can ask your queries from the bot using the slash command: ```text -/ec query +/query ``` 📝 Note: To use the bot privately, you can message the bot directly by right clicking the bot and selecting `Message`. diff --git a/embedchain/bots/discord.py b/embedchain/bots/discord.py new file mode 100644 index 00000000..e7993e5f --- /dev/null +++ b/embedchain/bots/discord.py @@ -0,0 +1,117 @@ +import logging +import os +import argparse + +import discord +from discord import app_commands +from discord.ext import commands + +from .base import BaseBot + +intents = discord.Intents.default() +intents.message_content = True +client = discord.Client(intents=intents) +tree = app_commands.CommandTree(client) + +# Invite link example +# https://discord.com/api/oauth2/authorize?client_id={DISCORD_CLIENT_ID}&permissions=2048&scope=bot + + +class DiscordBot(BaseBot): + def __init__(self, *args, **kwargs): + BaseBot.__init__(self, *args, **kwargs) + + def add_data(self, message): + data = message.split(" ")[-1] + try: + self.add(data) + response = f"Added data from: {data}" + except Exception: + logging.exception(f"Failed to add data {data}.") + response = "Some error occurred while adding data." + return response + + def ask_bot(self, message): + try: + response = self.query(message) + except Exception: + logging.exception(f"Failed to query {message}.") + response = "An error occurred. Please try again!" + return response + + def start(self): + client.run(os.environ["DISCORD_BOT_TOKEN"]) + + +# @tree decorator cannot be used in a class. A global discord_bot is used as a workaround. + + +@tree.command(name="question", description="ask embedchain") +async def query_command(interaction: discord.Interaction, question: str): + await interaction.response.defer() + member = client.guilds[0].get_member(client.user.id) + logging.info(f"User: {member}, Query: {question}") + try: + answer = discord_bot.ask_bot(question) + if args.include_question: + response = f"> {question}\n\n{answer}" + else: + response = answer + await interaction.followup.send(response) + except Exception as e: + await interaction.followup.send("An error occurred. Please try again!") + logging.error("Error occurred during 'query' command:", e) + + +@tree.command(name="add", description="add new content to the embedchain database") +async def add_command(interaction: discord.Interaction, url_or_text: str): + await interaction.response.defer() + member = client.guilds[0].get_member(client.user.id) + logging.info(f"User: {member}, Add: {url_or_text}") + try: + response = discord_bot.add_data(url_or_text) + await interaction.followup.send(response) + except Exception as e: + await interaction.followup.send("An error occurred. Please try again!") + logging.error("Error occurred during 'add' command:", e) + + +@tree.command(name="ping", description="Simple ping pong command") +async def ping(interaction: discord.Interaction): + await interaction.response.send_message("Pong", ephemeral=True) + + +@tree.error +async def on_app_command_error(interaction: discord.Interaction, error: discord.app_commands.AppCommandError) -> None: + if isinstance(error, commands.CommandNotFound): + await interaction.followup.send("Invalid command. Please refer to the documentation for correct syntax.") + else: + logging.error("Error occurred during command execution:", error) + + +@client.event +async def on_ready(): + # TODO: Sync in admin command, to not hit rate limits. + # This might be overkill for most users, and it would require to set a guild or user id, where sync is allowed. + await tree.sync() + logging.debug("Command tree synced") + logging.info(f"Logged in as {client.user.name}") + + +def start_command(): + parser = argparse.ArgumentParser(description="EmbedChain DiscordBot command line interface") + parser.add_argument( + "--include-question", + help="include question in query reply, otherwise it is hidden behind the slash command.", + action="store_true", + ) + global args + args = parser.parse_args() + + global discord_bot + discord_bot = DiscordBot() + discord_bot.start() + + +if __name__ == "__main__": + start_command() diff --git a/pyproject.toml b/pyproject.toml index b8593508..8a310742 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,6 +100,7 @@ elasticsearch = { version = "^8.9.0", optional = true } flask = "^2.3.3" twilio = "^8.5.0" fastapi-poe = { version = "0.0.16", optional = true } +discord = { version = "^2.3.2", optional = true } @@ -119,6 +120,7 @@ community = ["llama-index"] opensource = ["sentence-transformers", "torch", "gpt4all"] elasticsearch = ["elasticsearch"] poe = ["fastapi-poe"] +discord = ["discord"] [tool.poetry.group.docs.dependencies]