Skip to content

Commit

Permalink
1.2 release (#49)
Browse files Browse the repository at this point in the history
* bump version for pypi package generation to match git tag

* Fix version (#40)

* add runtime file for heroku

* allow any 3.7 sub version

* breaks heroku deploy, poetry build pack makes this file

* dummy commit

* version bump

* Update README (#42)

* Add some Slack and Docker detail to the README

Provide some more guidance around finding Slack IDs and tokens, and
running the included docker-based postgres server.

* Remove readme detail

* Allow `topchannels` command in private messages (#41)

Let people review the top channel list in the peace and comfort of their
own DMs :)

* add error handling for env vars; run pre-commit (#43)

* fix python version and enable test for PRs

* add a fake KARMABOT_DATABASE_URL to the nox env (#48)

* Improve joke command (#45)

* add error handling for env vars; run pre-commit

* improve joke command (issue #37): get list of categories from pyjokes module and find closest match with difflib

* refactor logic of category match into helper function with own tests

* version bump

* Update versions of python, nox, poetry

Co-authored-by: AJ Kerrigan <[email protected]>
Co-authored-by: Michael Aydinbas <[email protected]>
Co-authored-by: Patrick-Oliver Groß <[email protected]>
  • Loading branch information
4 people authored Sep 9, 2020
1 parent bd709e0 commit c09b7f4
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 42 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}}
15 changes: 11 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
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
- uses: actions/setup-python@v1
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
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
1 change: 1 addition & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "karmabot"
version = "0.1.0"
version = "1.2"
description = "A bot for Slack"
authors = ["PyBites <[email protected]>"]
license = "MIT"
Expand All @@ -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"
Expand Down
29 changes: 12 additions & 17 deletions src/karmabot/commands/joke.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import difflib
from typing import Union

import pyjokes

import karmabot

PYJOKE_HREF = "<https://pyjok.es/|PyJoke>"
CATEGORIES = list(pyjokes.jokes_en.jokes_en.keys())


def joke(**kwargs) -> Union[str, None]:
Expand All @@ -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
33 changes: 27 additions & 6 deletions src/karmabot/settings.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,44 @@
import logging
import os
import re
from pathlib import Path

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)

Expand Down
7 changes: 5 additions & 2 deletions src/karmabot/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand Down
22 changes: 20 additions & 2 deletions tests/test_karmabot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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

0 comments on commit c09b7f4

Please sign in to comment.