* Added base admin login/logout flows * Ignore local database * Channel model * Admin interface for channels and plugins * Added database tests along with workflows * Added some docstrings * Ignore .coverage file * Creating plugins docs WIP * Documentation * Black everything * Some documentation * Coverage for the plugins package as well * DB Fixes * Absolute FROM in Dockerfile * Database and logging fixes * Slack: Support private channels * Added pre-commit * black'd * Fixed UserQuery.create * Fixed ChannelPluginQuery.create exists call * Added ChannelPlugin menu for debugging * Ignore sqlite databases * Updated contributing docs
105 lines
3.2 KiB
Python
105 lines
3.2 KiB
Python
from datetime import datetime
|
|
|
|
import structlog
|
|
|
|
from butterrobot.platforms.base import Platform, PlatformMethods
|
|
from butterrobot.config import SLACK_TOKEN, SLACK_BOT_OAUTH_ACCESS_TOKEN
|
|
from butterrobot.objects import Message, Channel
|
|
from butterrobot.lib.slack import SlackAPI
|
|
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
|
|
class SlackMethods(PlatformMethods):
|
|
@classmethod
|
|
def send_message(self, message: Message):
|
|
logger.debug(
|
|
"Outgoing message", message=message.__dict__, platform=SlackPlatform.ID
|
|
)
|
|
try:
|
|
SlackAPI.send_message(
|
|
channel=message.chat, message=message.text, thread=message.reply_to
|
|
)
|
|
except SlackAPI.SlackClientError as error:
|
|
logger.error(
|
|
"Send message error",
|
|
platform=SlackPlatform.ID,
|
|
error=error,
|
|
message=message.__dict__,
|
|
)
|
|
|
|
|
|
class SlackPlatform(Platform):
|
|
ID = "slack"
|
|
|
|
methods = SlackMethods
|
|
|
|
@classmethod
|
|
def init(cls, app):
|
|
if not (SLACK_TOKEN and SLACK_BOT_OAUTH_ACCESS_TOKEN):
|
|
logger.error("Missing token. platform not enabled.", platform=cls.ID)
|
|
return
|
|
|
|
@classmethod
|
|
def parse_channel_name_from_raw(cls, channel_raw):
|
|
return channel_raw["name"]
|
|
|
|
@classmethod
|
|
def parse_channel_from_message(cls, message):
|
|
# Call different APIs for a channel or DM
|
|
if message["event"]["channel_type"] == "im":
|
|
chat_raw = SlackAPI.get_user_info(message["event"]["user"])
|
|
else:
|
|
chat_raw = SlackAPI.get_conversations_info(message["event"]["channel"])
|
|
|
|
return Channel(
|
|
platform=cls.ID,
|
|
platform_channel_id=message["event"]["channel"],
|
|
channel_raw=chat_raw,
|
|
)
|
|
|
|
@classmethod
|
|
def parse_incoming_message(cls, request):
|
|
data = request["json"]
|
|
|
|
# Auth
|
|
if data.get("token") != SLACK_TOKEN:
|
|
raise cls.PlatformAuthError("Authentication error")
|
|
|
|
# Confirms challenge request to configure webhook
|
|
if "challenge" in data:
|
|
raise cls.PlatformAuthResponse(data={"challenge": data["challenge"]})
|
|
|
|
# Discard messages by webhooks and apps
|
|
if "bot_id" in data["event"]:
|
|
logger.debug("Discarding message", data=data)
|
|
return
|
|
|
|
logger.debug("Parsing message", platform=cls.ID, data=data)
|
|
|
|
if data["event"]["type"] not in ("message", "message.groups"):
|
|
return
|
|
|
|
# Surprisingly, this *can* happen.
|
|
if "text" not in data["event"]:
|
|
return
|
|
|
|
message = Message(
|
|
id=data["event"].get("thread_ts", data["event"]["ts"]),
|
|
author=data["event"].get("user"),
|
|
from_bot="bot_id" in data["event"],
|
|
date=datetime.fromtimestamp(int(float(data["event"]["event_ts"]))),
|
|
text=data["event"]["text"],
|
|
chat=data["event"]["channel"],
|
|
channel=cls.parse_channel_from_message(data),
|
|
raw=data,
|
|
)
|
|
|
|
logger.info(
|
|
"New message",
|
|
platform=message.channel.platform,
|
|
channel=cls.parse_channel_name_from_raw(message.channel.channel_raw),
|
|
)
|
|
|
|
return message
|