import json
import logging
from cryptography.fernet import Fernet
from django.conf import settings
from django.shortcuts import reverse
from django.db import connection
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseServerError, JsonResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import get_user_model
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
from accounts.models import UserPreferences
from data.decorators import process_in_thread
from rt import api as rt_api
from . import views, models
logger = logging.getLogger(__name__)
# API Methods
[docs]def delete_file(id):
client = WebClient(token=settings.SLACK_TOKEN)
response = client.files_delete(file=id)
return response['ok']
[docs]def load_channels(archived=False):
"""
Get a list of all the public channels in Slack
:param archived: Boolean - Include archived channels
:returns: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.conversations_list(exclude_archived=not archived)
assert response['ok'] is True
channels = []
for channel in response['channels']:
channels.append((channel['id'], channel['name']))
return {'ok': True, 'channels': channels}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def channel_info(channel_id):
"""
Retrieves all the information about a channel
:param channel_id: The ID of the channel
:return: Channel details (Dictionary)
"""
if not settings.SLACK_TOKEN:
return None
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.conversations_info(channel=channel_id)
assert response['ok'] is True
return response['channel']
except SlackApiError as e:
assert e.response['ok'] is False
return None
[docs]def join_channel(channel):
"""
If the app gets the 'not_in_channel' error when accessing a public channel, call this method
:param channel: The channel to join
:returns: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.conversations_join(channel=channel)
assert response['ok'] is True
return {'ok': response['ok']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def upload(attachment, filename, title=None, message=None, channels=None):
"""
Upload a new file to Slack
:param attachment: File path to the file
:param filename: Filename with file extension (i.e. example.pdf)
:param title: Title of the file to display in Slack
:param message: The message text introducing the file in the specified ``channels``
:param channels: Comma-separated list of channel names or ids where the file should be posted (i.e. C1234567890)
:returns: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
client.timeout = 600
try:
if channels:
response = client.files_upload(channels=channels, file=attachment, filename=filename,
initial_comment=message, title=title)
else:
response = client.files_upload(file=attachment, filename=filename, title=title)
assert response['ok'] is True
return {'ok': True, 'file': response['file']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def slack_post(channel, thread=None, text=None, content=None, username=None, icon_url=None, attachment=None):
"""
Post a message on Slack
The `text` parameter is not required when the `content` parameter is provided, however including it is still
highly recommended.
:param channel: The identifier of the Slack conversation to post to
:param thread: The timestamp of another message to post this message as a reply to
:param text: Message text (Formatting: https://api.slack.com/reference/surfaces/formatting)
:param content: List of valid blocks data (https://api.slack.com/block-kit)
:param username: Name displayed by the bot
:param icon_url: The URL to an image / icon to display next to the message (profile picture)
:param attachment: Dictionary with file details - {'name': 'Example File', 'filepath': '/media/slack/example.pdf'}
:returns: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
if attachment:
filename = attachment['filepath'].split('/')[-1]
return upload(attachment['filepath'], filename, attachment['name'], text, channel)
if content:
try:
if username:
response = client.chat_postMessage(channel=channel, thread_ts=thread, blocks=content, text=text,
username=username, icon_url=icon_url)
else:
response = client.chat_postMessage(channel=channel, thread_ts=thread, blocks=content, text=text)
assert response['ok'] is True
return {'ok': True, 'message': response['message']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
elif text:
try:
if username:
response = client.chat_postMessage(channel=channel, thread_ts=thread, text=text, username=username,
icon_url=icon_url)
else:
response = client.chat_postMessage(channel=channel, thread_ts=thread, text=text)
assert response['ok'] is True
return {'ok': True, 'message': response['message']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
elif not content and not text:
return {'ok': False, 'error': 'no_text'}
[docs]def post_ephemeral(channel, text, user, username=None):
"""
Send an ephemeral message to a user in a channel. This message will only be visible to the target user.
:param channel: The identifier of the Slack conversation to post to
:param text: Message text (Formatting: https://api.slack.com/reference/surfaces/formatting)
:param user: The identifier of the specified user
:param username: Name displayed by the bot
:return: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.chat_postEphemeral(channel=channel, text=text, user=user, username=username)
assert response['ok'] is True
return response
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def message_react(channel, message, reaction):
"""
React to a Slack message
:param channel: The channel the message was posted to
:param message: The timestamp of the message
:param reaction: The name of the emoji to react to the message with
:return: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.reactions_add(channel=channel, timestamp=message, name=reaction)
assert response['ok'] is True
return response
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def message_unreact(channel, message, reaction):
"""
Remove a reaction from a Slack message
:param channel: The channel the message was posted to
:param message: The timestamp of the message
:param reaction: The name of the emoji to remove from the message
:return: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.reactions_remove(channel=channel, timestamp=message, name=reaction)
assert response['ok'] is True
return response
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def retrieve_message(channel, message_id):
"""
Retrieve a single message from Slack
:param channel: The channel the message was posted to
:param message_id: The timestamp of the message
:return: The message details
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.conversations_history(channel=channel, latest=message_id, inclusive=True, limit=1)
assert response['ok'] is True
return response
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def replace_message(channel, message_id, text=None, content=None):
"""
Replace an existing message in Slack. The message will need to have been published by the bot.
The `text` parameter is not required when the `content` parameter is provided, however including it is still
highly recommended.
:param channel: The identifier of the Slack conversation the message was posted to
:param message_id: The timestamp of the message to be updated
:param text: Message text (Formatting: https://api.slack.com/reference/surfaces/formatting)
:param content: List of valid blocks data (https://api.slack.com/block-kit)
:return: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
if content or text:
try:
response = client.chat_update(channel=channel, ts=message_id, as_user=True, text=text, blocks=content,
link_names=True)
assert response['ok'] is True
return {'ok': True, 'message': response['message']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
else:
return {'ok': False, 'error': 'no_text'}
[docs]def message_link(channel, message_id):
"""
Get a permalink for a specific message in Slack.
:param channel: The channel the message was posted in
:param message_id: The timestamp of the message
:return: Permalink URL
"""
if not settings.SLACK_TOKEN:
return None
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.chat_getPermalink(channel=channel, message_ts=message_id)
assert response['ok'] is True
return response['permalink']
except SlackApiError as e:
assert e.response['ok'] is False
return None
[docs]def user_add(channel, users):
"""
Invite users to join a slack channel. The bot must be a member of the channel.
:param channel: The identifier of the Slack channel to invite the users to
:param users: The identifiers of the specified users (List of up to 1000)
:return: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.conversations_invite(channel=channel, users=users)
assert response['ok'] is True
return {'ok': response['ok']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def user_kick(channel, user):
"""
Remove a user from a slack channel. The bot must be a member of the channel.
:param channel: The identifier of the Slack channel to remove users from
:param user: The identifier of the specified user
:return: Response object (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.conversations_kick(channel=channel, user=user)
assert response['ok'] is True
return {'ok': response['ok']}
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def user_profile(user_id):
"""
Get basic user profile information
:param user_id: The identifier for the user in Slack (i.e. U123456789)
:return: Slack user info (Dictionary)
"""
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.users_info(user=user_id)
assert response['ok'] is True
return response
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
[docs]def lookup_user(email):
"""
Will search for a user in the Slack workspace using their email address
:param email: The email address for the user
:return: The identifier for the user in Slack (`None` if the search returns nothing)
"""
if not settings.SLACK_TOKEN:
return None
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.users_lookupByEmail(email=email)
assert response['ok'] is True
return response['user']['id']
except SlackApiError as e:
assert e.response['ok'] is False
return None
[docs]def check_presence(user):
"""
Gets user presence information from Slack ("active" or "away")
:param user: The identifier of the specified user
:return: True if user is currently active, False if user is away
"""
if not settings.SLACK_TOKEN:
return None
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.users_getPresence(user=user)
assert response['ok'] is True
if response['presence'] == 'active':
return True
else:
return False
except SlackApiError as e:
assert e.response['ok'] is False
return None
[docs]def open_modal(trigger_id, blocks):
"""
Opens a modal view (in Slack) in response to user action
:param trigger_id: The trigger id provided by the API during the user's last interaction
:param blocks: Block configuration (https://api.slack.com/block-kit)
:return: View ID if successful; None otherwise
"""
if not settings.SLACK_TOKEN:
return None
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.views_open(trigger_id=trigger_id, view=blocks)
assert response['ok'] is True
return response['view']['id']
except SlackApiError as e:
assert e.response['ok'] is False
return None
# Event Handlers
[docs]@csrf_exempt
@require_POST
def handle_event(request):
"""
Event endpoint for the Slack API. Slack will send POST requests here whenever certain events have been triggered.
"""
payload = json.loads(request.body)
if payload['type'] == "url_verification":
return JsonResponse({"challenge": payload['challenge']})
elif payload['type'] == "event_callback":
event = payload['event']
if event['type'] == "team_join":
slack_post(event['user']['id'], text="Welcome to LNL!", content=views.welcome_message())
elif event['type'] == "app_home_opened":
load_app_home(event['user'])
elif event['type'] == "channel_created":
if settings.SLACK_AUTO_JOIN:
join_channel(event['channel']['id'])
return HttpResponse()
return HttpResponse("Not implemented")
@process_in_thread
def load_app_home(user_id):
"""
Load the App's Home tab.
:param user_id: The identifier for the user in Slack
:return: Response object (Dictionary)
"""
ticket_ids = []
tickets = []
user = user_profile(user_id)
if user['ok']:
email = user['user']['profile']['email']
ticket_ids = sorted(rt_api.simple_ticket_search(requestor=email, status="__Active__"), reverse=True)
for ticket_id in ticket_ids:
ticket = rt_api.fetch_ticket(ticket_id)
if ticket.get('message'):
continue
tickets.append(ticket)
blocks = views.app_home(tickets)
if not settings.SLACK_TOKEN:
return {'ok': False, 'error': 'config_error'}
client = WebClient(token=settings.SLACK_TOKEN)
try:
response = client.views_publish(user_id=user_id, view={"type": "home", "blocks": blocks})
assert response['ok'] is True
return response
except SlackApiError as e:
assert e.response['ok'] is False
return e.response
# Interaction handlers
[docs]@csrf_exempt
@require_POST
def handle_interaction(request):
"""
Interaction endpoint for the Slack API. Slack will send POST requests here when users interact with a shortcut or
interactive component.
"""
payload = json.loads(request.POST['payload'])
interaction_type = payload.get('type', None)
# Handle shortcut
if interaction_type == "shortcut":
callback_id = payload.get('callback_id', None)
if callback_id == "tfed":
blocks = views.tfed_modal()
modal_id = open_modal(payload.get('trigger_id', None), blocks)
if modal_id:
return HttpResponse()
return HttpResponseServerError("Failed to open modal")
if interaction_type == "message_action":
callback_id = payload.get('callback_id', None)
if callback_id == "report":
channel = payload.get('channel', {'id': None})['id']
sender = payload['message'].get('user', None)
if not sender:
sender = payload['message']['username']
ts = payload['message']['ts']
text = payload['message']['text']
message, created = models.SlackMessage.objects.get_or_create(posted_to=channel, posted_by=sender, ts=ts,
content=text)
blocks = views.report_message_modal(message)
modal_id = open_modal(payload.get('trigger_id', None), blocks)
if modal_id:
return HttpResponse()
return HttpResponseServerError("Failed to open modal")
# Handle modal view submission
if interaction_type == "view_submission":
values = payload['view']['state']['values']
callback_id = payload['view'].get('callback_id', None)
# TFed ticket submission
if callback_id == "tfed-modal":
subject = values['subject']['subject-action']['value']
description = values['description']['description-action']['value']
topic = values['rt_topic']['rt_topic-action']['selected_option']['value']
user_id = payload['user']['id']
user = user_profile(user_id)
if user['ok']:
__create_ticket(user, subject, description, topic)
return HttpResponse()
return HttpResponseServerError("Failed to obtain user information")
# Update TFed ticket
elif callback_id == "ticket-update-modal":
ticket_info = payload['view']['blocks'][1]
owner_id = None
if ticket_info['type'] != "divider":
ticket_info = payload['view']['blocks'][2]
owner_id = values['ticket_assignee']['ticket_assignee-action']['selected_user']
ticket_id = ticket_info['block_id'].split("#")[0]
channel = ticket_info['block_id'].split("#")[1]
ts = ticket_info['block_id'].split("#")[2]
status = values['ticket_status']['ticket_status-action']['selected_option']
if status:
status = status['value']
comments = values['ticket_comment']['ticket_comment-action']['value']
checkboxes = values['email_requestor']['email_requestor-action']['selected_options']
notify_requestor = False
if len(checkboxes) > 0:
notify_requestor = True
# Obtain user's RT token
user_id = payload['user']['id']
token = __retrieve_rt_token(user_id)
__update_ticket(ticket_id, status, owner_id, comments, notify_requestor, token, user_id, channel, ts)
return HttpResponse()
elif callback_id == "ticket-comment-modal":
ticket_id = payload['view']['blocks'][0]['block_id']
comments = values[ticket_id]['comment-action']['value']
user_id = payload['user']['id']
token = __retrieve_rt_token(user_id)
__post_ticket_comment(ticket_id, user_id, comments, token)
return HttpResponse()
elif callback_id == "report-modal":
message_id = payload['view']['blocks'][0]['block_id']
comments = values['report-comment']['comment-action']['value']
reporter = payload['user']['id']
__save_report(message_id, reporter, comments)
return HttpResponse()
return HttpResponseNotFound()
# Handle block interaction event
if interaction_type == "block_actions":
action = payload['actions'][0]['action_id']
channel = payload.get('channel', None)
if channel:
channel = channel['id']
message = payload.get('message', None)
view = payload.get('view', None)
# TFed message
if channel in [settings.SLACK_TARGET_TFED, settings.SLACK_TARGET_TFED_DB] and message and not view:
ticket_id = message['blocks'][0]['block_id'].split('~')[0]
blocks = views.ticket_update_modal(ticket_id, channel, message['ts'], action)
# Get current ticket from RT
__refresh_ticket_async(channel, message)
# Check that user has token, if not display a warning
user_id = payload['user']['id']
token = __retrieve_rt_token(user_id)
if not token:
error_message = "Hi there! Before you can update tickets, you'll need to set up access to your RT " \
"account. Visit https://lnl.wpi.edu" + reverse("support:link-account") + \
" to get started."
post_ephemeral(channel, error_message, user_id, 'Request Tracker')
return HttpResponse()
modal_id = open_modal(payload.get('trigger_id', None), blocks)
if modal_id:
return HttpResponse()
return HttpResponseServerError("Failed to open modal")
# Home tab menu options
if action == "home-ticket-update":
ticket_id = payload['actions'][0]['block_id']
option = payload['actions'][0]['selected_option']['value']
if option == 'Comment':
blocks = views.ticket_comment_modal(ticket_id)
modal_id = open_modal(payload.get('trigger_id', None), blocks)
if not modal_id:
return HttpResponseServerError("Failed to open modal")
return HttpResponse()
return HttpResponseNotFound()
@process_in_thread
def __create_ticket(user, subject, description, topic):
"""
Handler for creating a new TFed ticket
:param user: The user that submitted the ticket
:param subject: The ticket's subject line
:param description: The contents of the ticket
:param topic: The Queue in RT to post the ticket to
"""
target = settings.SLACK_TARGET_TFED
if topic == 'Database':
target = settings.SLACK_TARGET_TFED_DB
user_email = user['user']['profile'].get('email', 'lnl-no-reply@wpi.edu')
display_name = user['user']['profile']['real_name']
resp = rt_api.create_ticket(topic, user_email, subject, description + "\n\n- " + display_name)
ticket_id = resp.get('id', None)
if ticket_id:
ticket_info = {
"url": 'https://lnl-rt.wpi.edu/rt/Ticket/Display.html?id=' + ticket_id,
"id": ticket_id,
"subject": subject,
"description": description,
"status": "New",
"assignee": None,
"reporter": user['user']['name']
}
ticket = views.tfed_ticket(ticket_info)
slack_post(target, text=description, content=ticket, username='Request Tracker')
return
error_message = "Whoops! It appears something went wrong while attempting to submit your request. " \
"Please wait a few minutes then try again. If the problem persists, please email " \
"us directly at tfed@wpi.edu."
post_ephemeral(target, error_message, user['user']['id'], username="Request Tracker")
@process_in_thread
def __update_ticket(ticket_id, status, owner_id, comments, notify_requestor, token, user_id, channel, ts):
"""
Handler for updating an existing TFed ticket
:param ticket_id: The ticket number
:param status: The new status to assign to the ticket in RT
:param owner_id: The Slack user ID for the ticket owner (who the ticket will be assigned to)
:param comments: Comments to add to the ticket history
:param notify_requestor: If True, the ticket creator will receive an email with the comments
:param token: The RT auth token for the user that triggered this action
:param user_id: The Slack user ID for the user that triggered this action
:param channel: The identifier of the Slack channel this ticket was posted to
:param ts: The timestamp of the original ticket message in Slack
"""
# Update ticket metadata
owner = user_profile(owner_id)
username = ''
if owner['ok']:
username = owner['user']['profile'].get('email', '').split('@')[0]
resp = rt_api.update_ticket(ticket_id, token, status, username)
if rt_api.permission_error(resp):
error_message = "Sorry, it appears you do not have permission to perform this action."
post_ephemeral(channel, error_message, user_id, 'Request Tracker')
return
# Update ticket in Slack
current_message = retrieve_message(channel, ts)
if current_message.get('error', '') == 'not_in_channel':
join_channel(channel)
current_message = retrieve_message(channel, ts)
resp = refresh_ticket_message(channel, current_message['messages'][0])
if not resp['ok']:
logger.warning("Failed to update ticket in Slack. Please check RT to see if your changes were applied.")
# Post comments / replies, if applicable
if comments:
slack_user = user_profile(user_id)
display_name = slack_user['user']['profile']['real_name']
resp = rt_api.ticket_comment(ticket_id, comments + "\n\n- " + display_name, notify_requestor,
token=token)
if rt_api.permission_error(resp):
error_message = "Sorry, it appears you do not have permission to perform this action."
post_ephemeral(channel, error_message, user_id, 'Request Tracker')
return
profile_photo = slack_user['user']['profile']['image_original']
slack_post(channel, ts, comments, username=display_name, icon_url=profile_photo)
@process_in_thread
def __post_ticket_comment(ticket_id, user_id, comments, token):
"""
Comment on a TFed ticket (background process).
:param ticket_id: The ticket number
:param user_id: The Slack user ID for the user that triggered the action
:param comments: The comments to be added to the ticket
:param token: The RT auth token for the user that triggered the action (if applicable)
"""
user = user_profile(user_id)
display_name = user['user']['profile']['real_name']
rt_api.ticket_comment(ticket_id, comments + "\n\n- " + display_name, True, token=token)
[docs]def refresh_ticket_message(channel, message):
"""
Update a TFed ticket message with the latest information
:param channel: The channel the ticket was posted to
:param message: The original message object
:return: Response from Slack API after attempting to update the message
"""
ticket_id = message['blocks'][0]['block_id'].split('~')[0]
ticket_reporter = message['blocks'][0]['block_id'].split('~')[1]
ticket_description = message['blocks'][1]['text']['text']
ticket = rt_api.fetch_ticket(ticket_id)
if ticket.get('message'):
return {"ok": False}
ticket_owner = ticket['Owner']['id']
if ticket_owner == "Nobody":
ticket_owner = None
ticket_info = {
"url": 'https://lnl-rt.wpi.edu/rt/Ticket/Display.html?id=' + ticket_id,
"id": ticket_id,
"subject": ticket.get('Subject'),
"description": ticket_description,
"status": ticket.get('Status').capitalize(),
"assignee": ticket_owner,
"reporter": ticket_reporter
}
new_message = views.tfed_ticket(ticket_info)
return replace_message(channel, message['ts'], ticket_description, new_message)
@process_in_thread
def __refresh_ticket_async(channel, message):
"""
Update a TFed ticket message with the latest information in the background
:param channel: The channel the ticket was posted to
:param message: The original message object
:return: Response from Slack API after attempting to update the message
"""
resp = refresh_ticket_message(channel, message)
if not resp['ok']:
logger.warning("Failed to update ticket in Slack. Please check RT to see if your changes were applied.")
def __retrieve_rt_token(user_id):
"""
Retrieve a user's RT auth token (if it exists)
:param user_id: The Slack user's identifier
:return: Auth token; `None` if it doesn't exist
"""
slack_user = user_profile(user_id)
if slack_user['ok']:
username = slack_user['user']['profile'].get('email', '').split('@')[0]
user = get_user_model().objects.filter(username=username).first()
if user:
prefs = UserPreferences.objects.filter(user=user).first()
if prefs:
if prefs.rt_token:
cipher_suite = Fernet(settings.CRYPTO_KEY)
return cipher_suite.decrypt(prefs.rt_token.encode('utf-8')).decode('utf-8')
return None
@process_in_thread
def __save_report(message_id, reporter, comments):
"""
Create a report when a user reports a problematic Slack message
:param message_id: The primary key value of the corresponding SlackMessage object
:param reporter: Slack user ID for the user that reported the message
:param comments: Optional comments for the report
"""
message = models.SlackMessage.objects.get(pk=message_id)
# Ensure message was posted to public channel. For privacy reasons, we currently do not report private messages.
channel_details = channel_info(message.posted_to)
if channel_details['is_channel'] and not channel_details['is_private']:
report = models.ReportedMessage.objects.create(message=message, comments=comments, reported_by=reporter)
# Send Exec a notification
blocks = views.reported_message_notification(reporter, report)
slack_post(settings.SLACK_TARGET_EXEC, text="You have a new flagged message to review", content=blocks,
username="Admin Console")
# Add red flag to message (to inform sender their message has been reported)
# message_react(message.posted_to, message.ts, 'triangular_flag_on_post')
else:
message.public = False
message.save()
post_ephemeral(message.posted_to, "This feature currently does not support reporting private messages. Please "
"contact a member of the executive board directly.", reporter)
connection.close()