Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ python_requires = >=3.9
install_requires =
Django >= 3.0, <5
slack-sdk==3.19.2
# FIXME: make locks an optional dependency (and use, in the run_bot command)
django-database-locks < 1
django-logbasecommand < 1

[options.packages.find]
Expand Down
61 changes: 34 additions & 27 deletions slackbot/management/commands/run_bot.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import unicodedata

from database_locks import locked
from django.conf import settings
from django.db import close_old_connections
from logbasecommand.base import LogBaseCommand
from slackbot.base import MessageProcessor

from slack_sdk.socket_mode.response import SocketModeResponse
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.builtin.client import SocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
from slack_sdk.socket_mode.response import SocketModeResponse

from slackbot.base import MessageProcessor


@locked
class Command(LogBaseCommand):
help = 'Run Slack bot.'
help = "Run Slack bot."
client = None
web = None
my_name = None
Expand All @@ -22,14 +20,19 @@ class Command(LogBaseCommand):
processors = []

def add_arguments(self, parser):
parser.add_argument('-t', '--token', action='store', help='Use TOKEN instead of the one in settings')
parser.add_argument(
"-t",
"--token",
action="store",
help="Use TOKEN instead of the one in settings",
)

def post_message(self, **kwargs):
kwargs['as_user'] = kwargs.get('as_user', 1)
kwargs["as_user"] = kwargs.get("as_user", 1)
return self.web.chat_postMessage(**kwargs)

def post_ephemeral(self, **kwargs):
kwargs['as_user'] = kwargs.get('as_user', True)
kwargs["as_user"] = kwargs.get("as_user", True)
return self.web.chat_postEphemeral(**kwargs)

def handle_message(self, **payload):
Expand All @@ -38,18 +41,18 @@ def handle_message(self, **payload):
self.handle_message_really(**payload)

def handle_message_really(self, **payload):
event = payload.get('event')
event = payload.get("event")

channel = event.get('channel')
user = event.get('user')
thread_ts = event.get('thread_ts')
channel = event.get("channel")
user = event.get("user")
thread_ts = event.get("thread_ts")

if not channel:
return False

team = event.get('team')
ts = event.get('event_ts', event.get('ts', ''))
message = event.get('text', '')
team = event.get("team")
ts = event.get("event_ts", event.get("ts", ""))
message = event.get("text", "")
message = unicodedata.normalize("NFKD", message) # normalize stuff like non-breaking spaces (/xa0)

# if `as_user=True`, check user to make sure we do not reply to `self`
Expand All @@ -66,7 +69,7 @@ def handle_message_really(self, **payload):
for p in self.processors:
try:
# Only process threads if bot was pinged in the message
if thread_ts and f'<@{self.my_id}>' in message:
if thread_ts and f"<@{self.my_id}>" in message:
r = p.process(message, user=user, channel=channel, ts=thread_ts, raw=event)
else:
r = p.process(message, user=user, channel=channel, ts=ts, raw=event)
Expand All @@ -78,21 +81,21 @@ def handle_message_really(self, **payload):
if MessageProcessor.STOP in r:
break
except Exception as exc:
self.log_exception(f'Processor {str(p)} failed with {str(exc)} for message {message}')
self.log_exception(f"Processor {str(p)} failed with {str(exc)} for message {message}")

# If private DM
if channel[0] == 'D':
if channel[0] == "D":
if not team:
return False

# check if message was hidden, can't react to hidden messages
if not processed_at_least_one and not event.get('hidden'):
if not processed_at_least_one and not event.get("hidden"):
# React to User message with emojis if no results are found.
self.web.reactions_add(name='surface_not_found', channel=channel, timestamp=ts)
self.web.reactions_add(name="surface_not_found", channel=channel, timestamp=ts)
# FIXME generic message here please
self.post_ephemeral(
channel=channel,
text='SurfaceBot will respond based on the context, so try to include IPs, Hostnames, Applications Name in the message.',
text="SurfaceBot will respond based on the context, so try to include IPs, Hostnames, Applications Name in the message.",
user=user,
)

Expand All @@ -101,11 +104,11 @@ def handle_message_really(self, **payload):
def set_up(self, **payload):
data = self.web.auth_test()
self.my_id = data.get("user_id")
self.my_id_match = '<@%s>' % self.my_id
self.my_id_match = "<@%s>" % self.my_id
self.my_name = data.get("user")

self.processors = [x(self.client, self.web) for x in MessageProcessor.__subclasses__()]
self.stdout.write(f'Connected as {self.my_name}')
self.stdout.write(f"Connected as {self.my_name}")
self.stdout.write(
f"Processors: {','.join(f'{x.__class__.__module__}.{x.__class__.__name__}' for x in self.processors)}"
)
Expand All @@ -123,10 +126,14 @@ def handle(self, *args, **options):
from slack_sdk.web.client import WebClient

self.web = WebClient(token=settings.SLACKBOT_BOT_TOKEN)
self.client = SocketModeClient(app_token=settings.SLACKBOT_APP_TOKEN, web_client=self.web, logger=self.logger)
self.client = SocketModeClient(
app_token=settings.SLACKBOT_APP_TOKEN,
web_client=self.web,
logger=self.logger,
)
self.set_up()
self.client.socket_mode_request_listeners.append(self.process)
self.stdout.write('Connecting...\n')
self.stdout.write("Connecting...\n")
self.client.connect()

# don't stop this process.
Expand Down
79 changes: 38 additions & 41 deletions testapp/testapp/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '#+%2m&x5wbg5&5dy5==nv#x954l6&%n2)v=ib2^ry53-sk(7km'
SECRET_KEY = "#+%2m&x5wbg5&5dy5==nv#x954l6&%n2)v=ib2^ry53-sk(7km"

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
Expand All @@ -31,55 +31,54 @@
# Application definition

INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'database_locks',
'slackbot',
'testapp', # to load slack.py
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"slackbot",
"testapp", # to load slack.py
]

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]

ROOT_URLCONF = 'testapp.urls'
ROOT_URLCONF = "testapp.urls"

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]

WSGI_APPLICATION = 'testapp.wsgi.application'
WSGI_APPLICATION = "testapp.wsgi.application"


# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases

DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}

Expand All @@ -89,26 +88,26 @@

AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]


# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = "en-us"

TIME_ZONE = 'UTC'
TIME_ZONE = "UTC"

USE_I18N = True

Expand All @@ -120,13 +119,11 @@
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'

DATABASE_LOCKS_ENABLED = False
STATIC_URL = "/static/"

# this is used by the command test_setup to create a django user with this email
# so slackbot `check_auth` (permissions) can work
TEST_USER_SLACK_EMAIL = ''
TEST_USER_SLACK_EMAIL = ""

try:
from .local_settings import *
Expand Down
1 change: 1 addition & 0 deletions testapp/testapp/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

from django.contrib import admin
from django.urls import path

Expand Down