diff --git a/.dockerignore b/.dockerignore index 94d8397..b31e66c 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,6 @@ .github +.docs .vscode .pytest_cache .ruff_cache -*.md \ No newline at end of file +*.md diff --git a/.docs/print_bot_working.png b/.docs/print_bot_working.png new file mode 100644 index 0000000..94ec5c4 Binary files /dev/null and b/.docs/print_bot_working.png differ diff --git a/Makefile b/Makefile index e666b82..dd352b0 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ endif APP_NAME="slack-bot-no-cpf" IMAGE_NAME="slack-bot-no-cpf" VERSION="latest" +MAIN_ENTRYPOINT="src/bot.py" ################################ # COMMANDS TO RUN LOCALLY @@ -25,7 +26,7 @@ local/lint/fix: poetry run ruff . --fix --exit-non-zero-on-fix local/run: - poetry run python src/main.py + poetry run python ${MAIN_ENTRYPOINT} ############################################ # COMMANDS TO RUN USING DOCKER (RECOMMENDED) @@ -50,7 +51,7 @@ docker/lint/fix: docker-compose run ${APP_NAME} poetry run ruff . --fix --exit-non-zero-on-fix docker/run: - docker-compose run ${APP_NAME} poetry run python src/main.py + docker-compose run ${APP_NAME} poetry run python ${MAIN_ENTRYPOINT} #################################### # DOCKER IMAGE COMMANDS diff --git a/README.md b/README.md index 02d21be..6794d00 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,40 @@ A simple slack bot that analyzes messages sent on channels and warns about possi - [Docker Compose](https://docs.docker.com/compose/) - **pre-requisite** - [Poetry](https://python-poetry.org/) - **pre-requisite** - [Ruff](https://github.com/astral-sh/ruff) +- [Slack Bolt](https://pypi.org/project/slack-bolt/) *Please pay attention on **pre-requisites** resources that you must install/configure.* +## How to create and configure a new Slack Bot + +You can find the official docs [here](https://api.slack.com/start/building/bolt-python). + +This bot needs to have the following User Token Scopes added: +``` +app_mentions:read +channels:history +channels:read +chat:write +im:history +im:read +``` + +Follow this [guide](https://api.slack.com/tutorials/tracks/getting-a-token) to generate the tokens. + +## How bot works on slack workspace + +![Bot working](.docs/print_bot_working.png) + ## How to install, run and test ### Environment variables -*Use this section to explain each env variable available on your application* - Variable | Description | Available Values | Default Value | Required --- | --- | --- | --- | --- ENV | The application enviroment | `dev / test / qa / prod` | `dev` | Yes PYTHONPATH | Provides guidance to the Python interpreter about where to find libraries and applications | [ref](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH) | `.` | Yes +SLACK_BOT_TOKEN | The slack bot token | `a valid token` | `-` | Yes +SLACK_APP_TOKEN | The slack app token | `a valid token` | `-` | Yes *Note: When you run the install command (using docker or locally), a .env file will be created automatically based on [env.template](env.template)* diff --git a/env.template b/env.template index b7bb1cb..b1815d1 100644 --- a/env.template +++ b/env.template @@ -1,2 +1,4 @@ ENV=dev -PYTHONPATH=. \ No newline at end of file +PYTHONPATH=. +SLACK_BOT_TOKEN= +SLACK_APP_TOKEN= diff --git a/pyproject.toml b/pyproject.toml index ba9cca3..cca10f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,8 @@ license = "MIT" [tool.poetry.dependencies] python = "^3.11" +slack-bolt = "1.18.1" +aiohttp = "3.9.3" [tool.poetry.dev-dependencies] pytest = "^8.0.0" @@ -19,7 +21,7 @@ pythonpath = ["src",] [tool.coverage.run] branch = true -omit = ["*/tests/*"] +omit = ["*/tests/*", "src/bot.py"] [tool.coverage.report] show_missing = true diff --git a/src/bot.py b/src/bot.py new file mode 100644 index 0000000..6910a7b --- /dev/null +++ b/src/bot.py @@ -0,0 +1,33 @@ +from asyncio import run as async_run +from logging import getLogger +from logging.config import fileConfig as logConfig +from os import getenv + +from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler +from slack_bolt.async_app import AsyncApp + +from src.messages.constants import DEFAULT_WARNING_MESSAGE +from src.rules.pattern import pattern + +logConfig("./logging.conf", disable_existing_loggers=False) +logger = getLogger(__name__) + +SLACK_BOT_TOKEN = getenv("SLACK_BOT_TOKEN", "").strip() +SLACK_APP_TOKEN = getenv("SLACK_APP_TOKEN", "").strip() + +app = AsyncApp(token=SLACK_BOT_TOKEN) + +@app.message(pattern.compiled_pattern) +async def say_hello_regex(say, message, client): + user = message['user'] + await say(text=DEFAULT_WARNING_MESSAGE.format(name=f'<@{user}>'), thread_ts=message.get('ts')) + +async def main(): + try: + handler = AsyncSocketModeHandler(app, SLACK_APP_TOKEN) + await handler.start_async() + except Exception as ex: + logger.error("Error when starting the bot", ex) + +if __name__ == "__main__": # pragma: no cover + async_run(main()) diff --git a/src/main.py b/src/main.py deleted file mode 100755 index 73d958d..0000000 --- a/src/main.py +++ /dev/null @@ -1,16 +0,0 @@ -from logging import getLogger -from logging.config import fileConfig as logConfig - -from config.settings import settings - -logConfig("./logging.conf", disable_existing_loggers=False) -logger = getLogger(__name__) - - -def hello() -> str: - logger.info(f"Hello from {settings.get('app_name')}") - return "Hello" - - -if __name__ == "__main__": # pragma: no cover - print(hello()) diff --git a/src/messages/constants.py b/src/messages/constants.py new file mode 100644 index 0000000..10fff0b --- /dev/null +++ b/src/messages/constants.py @@ -0,0 +1,3 @@ +# todo: we will use locale (gnu gettext) to improve the messages +# ruff: noqa: E501 +DEFAULT_WARNING_MESSAGE='Olá {name}, por favor não envie CPF, email, telefone (ou qualquer outro dado sensível) em canais públicos aqui pelo slack' diff --git a/tests/test_main.py b/tests/test_main.py deleted file mode 100644 index 09d5fd4..0000000 --- a/tests/test_main.py +++ /dev/null @@ -1,8 +0,0 @@ -from unittest import TestCase - -from src.main import hello - - -class MainTest(TestCase): - def test_main_hello(self): - self.assertEqual(hello(), "Hello")