diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 7ed0e06..6955d08 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -7,10 +7,10 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-python@v1 with: - python-version: "3.7.8" + python-version: "3.7" architecture: x64 - - run: pip install nox==2019.11.9 - - run: pip install poetry==1.0.5 + - run: pip install nox==2020.8.22 + - run: pip install poetry==1.0.10 - run: nox --sessions tests-3.7 coverage env: CODECOV_TOKEN: ${{secrets.CODECOV_TOKEN}} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ef25da1..e2533f8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,11 +1,18 @@ name: Tests -on: push +on: + push: + branches: + - master + - develop + pull_request: + branches: + - develop jobs: tests: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.7.8'] + python-version: [3.7] name: Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v2 @@ -13,6 +20,6 @@ jobs: with: python-version: ${{ matrix.python-version }} architecture: x64 - - run: pip install nox==2019.11.9 - - run: pip install poetry==1.0.5 + - run: pip install nox==2020.8.22 + - run: pip install poetry==1.0.10 - run: nox diff --git a/README.md b/README.md index 2c74cba..2d96b66 100644 --- a/README.md +++ b/README.md @@ -47,16 +47,18 @@ KARMABOT_ADMINS= ``` - KARMABOT_SLACK_USER - The [bot's slack user id](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace). + The [bot's slack user id](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace). Once you've created your own Karmabot app, you can view its configuration details from the [My Apps](https://api.slack.com/apps/) page. The user ID shows up under **Basic Information --> App Credentials --> App ID**. - KARMABOT_SLACK_TOKEN - The [auth toke](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace) for your bot + The [auth token](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace) for your bot. You can find the token from the [My Apps](https://api.slack.com/apps/) page for your Karmabot under **OAuth & Permissions --> Tokens for Your Workspace --> Bot User OAuth Access Token**. It starts with `xoxb-`. - KARMABOT_SLACK_INVITE_USER_TOKEN An invite token to invite the bot to new channels. Bots cannot autojoin channels, but we implemented an invite procedure for this. - KARMABOT_DATABASE_URL - The database url which should be compatible with SqlAlchemy. For the provided docker file use postgres://user42:pw42@localhost:5432/karmabot + The database url which should be compatible with SqlAlchemy. For the provided docker file use postgres://user42:pw42@localhost:5432/karmabot. + + - **Note:** To start the provided Docker-based Postgres server, be sure you have Docker Compose [installed](https://docs.docker.com/compose/install/) and run `docker-compose up` from the karmabot directory. - KARMABOT_GENERAL_CHANNEL The channel id of your main channel slack @@ -81,10 +83,14 @@ The poetry virtual environment should be available in the the project folder as For testing you need to install [nox](https://nox.thea.codes/en/stable/) separately from the project venv created by poetry. For testing just use the `nox` command within the project folder. You can run all the nox sessions separately if need, e.g., - only linting `nox -rs lint` -- only testing `nox -rs test` +- only testing `nox -rs tests` + +For different sessions see the `nox.py` file. You can run `nox --list` to see a list of all available sessions. -For different sessions see the `nox.py` file. Please make sure all tests and checks pass before opening pull requests! +Please make sure all tests and checks pass before opening pull requests! ### [pre-commit](https://pre-commit.com/) -To ensure consistency you can use pre-commit. `pip install pre-commit` and after cloning the karmabot repo run `pre-commit install` within the project folder. This will enable pre-commit hooks for checking before every commit. +To ensure consistency you can use pre-commit. `pip install pre-commit` and after cloning the karmabot repo run `pre-commit install` within the project folder. + +This will enable pre-commit hooks for checking before every commit. diff --git a/noxfile.py b/noxfile.py index cad7327..cf99713 100644 --- a/noxfile.py +++ b/noxfile.py @@ -13,6 +13,7 @@ "KARMABOT_SLACK_INVITE_USER_TOKEN": "FAKE_INVITE_USER_TOKEN", "KARMABOT_GENERAL_CHANNEL": "GENERAL", "KARMABOT_ADMINS": "FAKE_ADMIN", + "KARMABOT_DATABASE_URL": "FAKE_URL", } nox.options.sessions = "lint", "mypy", "safety", "tests" diff --git a/pyproject.toml b/pyproject.toml index 0942d0f..18d9d5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "karmabot" -version = "1.0" +version = "1.2" description = "A bot for Slack" authors = ["PyBites "] license = "MIT" @@ -10,7 +10,7 @@ repository = "https://github.com/PyBites-Open-Source/karmabot" keywords = ["karmabot"] [tool.poetry.dependencies] -python = "3.7.8" +python = "^3.7" humanize = "^2.4.0" pyjokes = "^0.6.0" SQLAlchemy = "^1.3.17" diff --git a/src/karmabot/commands/joke.py b/src/karmabot/commands/joke.py index 64b3654..1a621e1 100644 --- a/src/karmabot/commands/joke.py +++ b/src/karmabot/commands/joke.py @@ -1,3 +1,4 @@ +import difflib from typing import Union import pyjokes @@ -5,6 +6,7 @@ import karmabot PYJOKE_HREF = "" +CATEGORIES = list(pyjokes.jokes_en.jokes_en.keys()) def joke(**kwargs) -> Union[str, None]: @@ -17,28 +19,21 @@ def joke(**kwargs) -> Union[str, None]: if id_arg is not None and text_arg is not None: user_id = karmabot.slack.format_user_id(str(id_arg)) - user_text = str(text_arg) + user_text = str(text_arg).lower() + + user_category = user_text.split()[-1] if len(user_text.split()) > 3 else "" + category = _get_closest_category(user_category) + else: return None - choice = "all" - if "chuck" in user_text.lower(): - choice = "chuck" - if "neutral" in user_text.lower(): - choice = "neutral" - - joke_text = pyjokes.get_joke(category=choice) + joke_text = pyjokes.get_joke(category=category) return f"Hey {user_id}, here is a {PYJOKE_HREF} for you: _{joke_text}_" -if __name__ == "__main__": - - output = joke(user_id=123, text="42") - print(output) - - output = joke(user_id=123, text="chuck") - print(output) +def _get_closest_category(input: str): + category = difflib.get_close_matches(input, CATEGORIES) + category = category[0] if category else "all" - output = joke(user_id=123, text="neutral") - print(output) + return category diff --git a/src/karmabot/settings.py b/src/karmabot/settings.py index 16f1039..5ada574 100644 --- a/src/karmabot/settings.py +++ b/src/karmabot/settings.py @@ -1,3 +1,4 @@ +import logging import os import re from pathlib import Path @@ -5,19 +6,39 @@ from dotenv import load_dotenv from slackclient import SlackClient + +def _get_env_var(env_var: str): + env_var_value = os.environ.get(env_var) + + # explicit check for None as None is returned by environ.get for non existing keys + if env_var_value is None: + raise KeyError( + f"{env_var} was not found. Please check your .karmabot file as well as the README.md." + ) + + if not env_var_value: + raise ValueError( + f"{env_var} was found but seems empty. Please check your .karmabot file as well as the README.md." + ) + + return env_var_value + + dotenv_path = Path(".").resolve() / ".karmabot" if dotenv_path.exists(): + logging.info("Loading environmental variables from .karmabot.") load_dotenv(dotenv_path) # Environment variables -KARMABOT_ID = os.environ.get("KARMABOT_SLACK_USER") -SLACK_TOKEN = os.environ.get("KARMABOT_SLACK_TOKEN") -SLACK_INVITE_TOKEN = os.environ.get("KARMABOT_SLACK_INVITE_USER_TOKEN") -DATABASE_URL = os.environ.get("KARMABOT_DATABASE_URL") +KARMABOT_ID = _get_env_var("KARMABOT_SLACK_USER") +SLACK_TOKEN = _get_env_var("KARMABOT_SLACK_TOKEN") +SLACK_INVITE_TOKEN = _get_env_var("KARMABOT_SLACK_INVITE_USER_TOKEN") +DATABASE_URL = _get_env_var("KARMABOT_DATABASE_URL") # Slack -GENERAL_CHANNEL = os.environ.get("KARMABOT_GENERAL_CHANNEL") -ADMINS = os.environ.get("KARMABOT_ADMINS").split(",") # type: ignore +GENERAL_CHANNEL = _get_env_var("KARMABOT_GENERAL_CHANNEL") +ADMINS = _get_env_var("KARMABOT_ADMINS") +ADMINS = ADMINS.split(",") # type: ignore SLACK_ID_FORMAT = re.compile(r"^<@[^>]+>$") SLACK_CLIENT = SlackClient(SLACK_TOKEN) diff --git a/src/karmabot/slack.py b/src/karmabot/slack.py index ce43a7a..975ab5a 100644 --- a/src/karmabot/slack.py +++ b/src/karmabot/slack.py @@ -42,6 +42,7 @@ "doc": doc_command, "help": create_commands_table, "karma": get_karma, + "topchannels": get_recommended_channels, "updateusername": update_username, "username": get_user_name, "joke": joke, @@ -149,8 +150,8 @@ def post_msg(channel_or_user_id: str, text) -> None: def bot_joins_new_channel(channel_id: str) -> None: """Bots cannot autojoin channels, but there is a hack: create a user token: - https://stackoverflow.com/a/44107313/1128469 and - https://api.slack.com/custom-integrations/legacy-tokens""" + https://stackoverflow.com/a/44107313/1128469 and + https://api.slack.com/custom-integrations/legacy-tokens""" grant_user_token = os.environ.get("SLACK_KARMA_INVITE_USER_TOKEN") if not grant_user_token: logging.info("Cannot invite bot, no env SLACK_KARMA_INVITE_USER_TOKEN") @@ -247,6 +248,8 @@ def parse_next_msg(): channel_id = msg.get("channel") text = msg.get("text") + logging.info(f"Parsing message {text} in channel {channel_id} from user {user_id}.") + # handle events first event_type = msg.get("type") # 1. if new channel auto-join bot diff --git a/tests/test_karmabot.py b/tests/test_karmabot.py index ad628e4..5784c50 100644 --- a/tests/test_karmabot.py +++ b/tests/test_karmabot.py @@ -8,6 +8,7 @@ from sqlalchemy.orm import Session import karmabot.commands.topchannels +from karmabot.commands.joke import _get_closest_category from karmabot.commands.topchannels import Channel, calc_channel_score from karmabot.commands.welcome import welcome_user from karmabot.db import db_session @@ -63,7 +64,7 @@ def karma_users(): @pytest.fixture def empty_db_session(engine, tables): - """ Returns an SQLAlchemy session, and after the tests + """Returns an SQLAlchemy session, and after the tests tears down everything properly. """ connection = engine.connect() @@ -83,7 +84,7 @@ def empty_db_session(engine, tables): @pytest.fixture def filled_db_session(engine, tables, karma_users): - """ Returns an SQLAlchemy session, and after the tests + """Returns an SQLAlchemy session, and after the tests tears down everything properly. """ connection = engine.connect() @@ -353,3 +354,20 @@ def test_ignore_message_subtypes(mock_slack_api_call, frozen_now): all_ignored = SLACK_CLIENT.api_call("channels.info", channel="ONLYJOINS") assert _channel_score(latest_ignored) > 0 assert _channel_score(all_ignored) == 0 + + +@pytest.mark.parametrize( + "user_category, expected", + [ + ("all", "all"), + ("neutral", "neutral"), + ("chuck", "chuck"), + ("", "all"), + ("al", "all"), + ("neutr", "neutral"), + ("chuk", "chuck"), + ("help", "all"), + ], +) +def test_get_closest_category(user_category, expected): + assert _get_closest_category(user_category) == expected