From 0c8e8ce31296622e644c0bddd3830e384388d494 Mon Sep 17 00:00:00 2001 From: Jan Vorcak Date: Tue, 18 Feb 2020 14:46:01 +0100 Subject: [PATCH] Events API support fixes #2 --- setup.py | 2 +- slack_app/decorators.py | 57 +++++++++++++++++++++++++++++++++++++++++ slack_app/signals.py | 10 ++++++++ slack_app/urls.py | 4 ++- slack_app/views.py | 23 +++++++++++++++++ 5 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 slack_app/signals.py diff --git a/setup.py b/setup.py index bec1724..f8b1dd3 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from setuptools import setup setup( - version="1.0.34", + version="1.0.35", install_requires=["slackclient"] ) diff --git a/slack_app/decorators.py b/slack_app/decorators.py index 92cac73..19a6d59 100644 --- a/slack_app/decorators.py +++ b/slack_app/decorators.py @@ -1,8 +1,12 @@ from functools import wraps +import slack from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponseBadRequest +from .signals import slack_event_received, refresh_home +from .models import SlackUserMapping, SlackWorkspace + from .helpers import is_verified_slack_request, slack_interactivity_callbacks, slack_commands @@ -37,3 +41,56 @@ def wrapped_view(request, *args, **kwargs): view_func.csrf_exempt = True return wraps(view_func)(wrapped_view) + + +def on_slack_signal(*event_types, inject_slack_models=False): + def decorator_receiver(receiver_func): + @wraps(receiver_func) + def signal_receiver(sender, event_type, event_data, signal, **kwargs): + if event_type in event_types: + slack_models = { + "slack_user_mapping": SlackUserMapping.objects.get(pk=event_data.get('user')), + "slack_workspace": SlackWorkspace.objects.get(pk=kwargs.get('team_id')), + } if inject_slack_models else {} + return receiver_func(sender, event_data=event_data, event_type=event_type, **slack_models, **kwargs) + + slack_event_received.connect(signal_receiver, weak=False) + + return decorator_receiver + + +def slack_app_home(receiver_func): + @wraps(receiver_func) + def signal_receiver( + sender, + event_type=None, + event_data=None, + + slack_user_mapping=None, + slack_workspace=None, + **kwargs + ): + if event_type == 'app_home_opened': + slack_user_mapping = SlackUserMapping.objects.get(pk=event_data.get('user')) + slack_workspace = SlackWorkspace.objects.get(pk=kwargs.get('team_id')) + + blocks, title = receiver_func( + sender, + event_data=event_data, + event_type=event_type, + slack_user_mapping=slack_user_mapping, + slack_workspace=slack_workspace, + **kwargs, + ) + + client = slack.WebClient(token=slack_workspace.bot_access_token) + client.views_publish(user_id=slack_user_mapping.slack_user_id, view={ + "type": 'home', + "title": title, + "blocks": blocks + }) + + slack_event_received.connect(signal_receiver, weak=False) + refresh_home.connect(signal_receiver, weak=False) + + return signal_receiver diff --git a/slack_app/signals.py b/slack_app/signals.py new file mode 100644 index 0000000..dfd7240 --- /dev/null +++ b/slack_app/signals.py @@ -0,0 +1,10 @@ +import django.dispatch + +""" +Slack want us to implement a queue to react to events. +https://api.slack.com/events-api#responding_to_events + +That's why, we've chosen to implement this using Django's signals. +""" +slack_event_received = django.dispatch.Signal(providing_args=["event_type", "event_data"]) +refresh_home = django.dispatch.Signal(providing_args=["slack_user_mapping", "slack_workspace"]) diff --git a/slack_app/urls.py b/slack_app/urls.py index f565bd1..4e707c0 100644 --- a/slack_app/urls.py +++ b/slack_app/urls.py @@ -1,6 +1,7 @@ from django.urls import path -from .views import slack_oauthcallback, slack_login_callback, slack_interactivity, slack_command, connect_account +from .views import slack_oauthcallback, slack_login_callback, slack_interactivity, slack_command, connect_account, \ + slack_events app_name = "slack_app" @@ -8,6 +9,7 @@ path('install/', slack_oauthcallback, name='install'), path('login/', slack_login_callback, name='login'), path('interactivity/', slack_interactivity, name='interactivity'), + path('events/', slack_events, name='events'), path('commands//', slack_command, name='command'), path('connect//', connect_account, name='connect_account'), ] \ No newline at end of file diff --git a/slack_app/views.py b/slack_app/views.py index 71860c7..de1b399 100644 --- a/slack_app/views.py +++ b/slack_app/views.py @@ -11,6 +11,7 @@ from django.urls import reverse from django.views.decorators.http import require_http_methods +from .signals import slack_event_received from .exceptions import SlackAppNotInstalledProperlyException, SlackAccountNotLinkedException, SlackCommandDoesNotExist, \ SlackInteractivityTypeDoesNotExist from .models import SlackWorkspace, SlackUserMapping @@ -160,3 +161,25 @@ def connect_account(request, nonce: str): # TODO - allow to specify a function that accepts request, mapping & user and return whatever it wants # (Redirect/Render, etc) return render(request, "verified_nonce.html") + + +@slack_verify_request +def slack_events(request): + data = json.loads(request.body) + + if data.get("type") == "url_verification": + return JsonResponse({ + "challenge": data.get("challenge"), + }) + + if data.get("type") == "event_callback": + event_data = data.pop('event') + event_type = event_data.pop('type') + + slack_event_received.send(sender=request, event_type=event_type, event_data=event_data, **data) + return JsonResponse({}) + + if data.get("type") == 'app_rate_limited': + return JsonResponse({}) + + return NotImplementedError(f"Unknown type {type}") \ No newline at end of file