示例#1
0
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'}
示例#2
0
class Slack:
    def __init__(self):
        load_dotenv()
        self.message_template = {
            "info": {
                "emoji": ":large_blue_circle:",
                "header": "Info",
            },
            "danger": {
                "emoji": ":red_circle:",
                "header": "Error",
            }
        }

        self.channel = 'C011C9E9RC7'
        self.client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
        self.last_ts = None

    def post(self, message, mtype):
        blocks = self.generate_blocks(message, mtype)
        response = self.client.chat_postMessage(channel=self.channel,
                                                blocks=blocks)
        self.set_ts(response)

    def update(self, message, mtype):
        blocks = self.generate_blocks(message, mtype)
        response = self.client.chat_update(channel=self.channel,
                                           ts=self.last_ts,
                                           blocks=blocks)
        self.set_ts(response)

    def set_ts(self, resp):
        self.last_ts = resp['ts']

    def generate_blocks(self, message, mtype):
        blocks = []
        blocks.append({
            "type": "section",
            "text": {
                "type":
                "mrkdwn",
                "text":
                f"{self.message_template[mtype]['emoji']}*{self.message_template[mtype]['header']}*: {message}"
            }
        })
        return blocks
示例#3
0
class SlackApp:
    # Slack client for Web API requests
    client = WebClient()

    SLACK_BOT_TOKEN = None
    SLACK_VERIFICATION_TOKEN = None
    SLACK_SIGNING_SECRET = None
    SLACK_CHANNEL_ID = None
    BUCKET_PATH = None

    def __init__(self, SLACK_BOT_TOKEN, SLACK_VERIFICATION_TOKEN,
                 SLACK_SIGNING_SECRET, SLACK_CHANNEL_ID, BUCKET_PATH):
        self.SLACK_BOT_TOKEN = SLACK_BOT_TOKEN
        self.SLACK_VERIFICATION_TOKEN = SLACK_VERIFICATION_TOKEN
        self.SLACK_SIGNING_SECRET = SLACK_SIGNING_SECRET
        self.SLACK_CHANNEL_ID = SLACK_CHANNEL_ID
        self.BUCKET_PATH = BUCKET_PATH

        self.client = WebClient(self.SLACK_BOT_TOKEN)

    def list_goals(self):
        request_json_py = {"active": "true"}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        goals_response = GetGoals(request_json)
        goals_json = goals_response.get_json()

        var_today = datetime.now(pytz.timezone("Africa/Johannesburg"))
        var_today_str = var_today.strftime("%Y-%m-%d")

        parent_message = self.client.chat_postMessage(
            channel=self.SLACK_CHANNEL_ID,
            blocks=[
                SlackComposer.MessageBlock_Divider(),
                SlackComposer.MessageBlock_TextHeader(
                    ":dart: You have {} goals ({})".format(
                        len(goals_json), var_today_str))
            ])

        parent_ts = parent_message["ts"]

        for index in goals_json:
            self.client.chat_postMessage(channel=self.SLACK_CHANNEL_ID,
                                         blocks=SlackComposer.Attachment_Goal(
                                             goals_json[index]),
                                         reply_broadcast="false",
                                         thread_ts=parent_ts)
        return make_response("", 200)

    def list_transactions(self):
        request_json_py = {"only_uncategorised": "True"}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        transactions_response = GetTransactions(request_json)
        transactions_json = transactions_response.get_json()

        var_today = datetime.now(pytz.timezone("Africa/Johannesburg"))
        var_today_str = var_today.strftime("%Y-%m-%d")

        parent_message = self.client.chat_postMessage(
            channel=self.SLACK_CHANNEL_ID,
            blocks=[
                SlackComposer.MessageBlock_Divider(),
                SlackComposer.MessageBlock_TextHeader(
                    ":construction: You have {} uncategorised transactions ({})"
                    .format(len(transactions_json), var_today_str))
            ])

        parent_ts = parent_message["ts"]

        for index in transactions_json:
            self.client.chat_postMessage(
                channel=self.SLACK_CHANNEL_ID,
                attachments=SlackComposer.Attachment_Transaction(
                    transactions_json[index]),
                reply_broadcast="false",
                thread_ts=parent_ts)

        return make_response("", 200)

    def post_transactions_to_channel(self, transaction_ids):
        request_json = json.loads(transaction_ids)

        transactions_response = GetTransactions(request_json)

        transactions_json = transactions_response.get_json()

        for index in transactions_json:
            self.client.chat_postMessage(
                channel=self.SLACK_CHANNEL_ID,
                attachments=SlackComposer.Attachment_Transaction(
                    transactions_json[index]),
                reply_broadcast="false")

        return make_response("", 200)

    def categorise_transaction(self, transaction_uuid, category_id,
                               message_ts):
        # Get transaction
        request_json_py = {"doc_id": transaction_uuid}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        response = GetTransactions(request_json)
        transaction_json = response.get_json()
        transaction_json["0"]["budget_category"] = category_id

        # Update transaction
        UpdateTransaction(transaction_json["0"], transaction_uuid)

        # Get transaction
        request_json_py = {"doc_id": transaction_uuid}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        response = GetTransactions(request_json)
        transaction_json = response.get_json()

        # Update transaction on Slack
        self.client.chat_update(
            channel=self.SLACK_CHANNEL_ID,
            ts=message_ts,
            attachments=SlackComposer.Attachment_Transaction(
                transaction_json["0"]),
        )
        return make_response("", 200)

    def delete_goal(self, goal_id, message_ts):
        request_json_py = {"doc_ids": [goal_id]}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        DeleteGoals(request_json)

        self.client.chat_update(
            channel=self.SLACK_CHANNEL_ID,
            ts=message_ts,
            blocks=[{
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "*DELETED:* ~{}~".format(goal_id),
                },
            }],
        )
        return make_response("", 200)

    def refresh_goal_modal(self, current_goal_data, view_id, goal_id,
                           message_ts, title, description):
        # request_json_py = {"active": "true", "doc_id": goal_id}
        # request_json_str = json.dumps(request_json_py)
        # request_json = json.loads(request_json_str)
        # goals_response = GetGoals(request_json)
        # goals_json = goals_response.get_json()

        goal = Goal()
        goal.setup_goal(current_goal_data)
        view = SlackComposer.Modal_Goal(title, description, goal, goal_id,
                                        message_ts)

        self.client.views_update(view=view, view_id=view_id)
        return make_response("", 200)

    def new_goal_modal(self, trigger_id):
        view = SlackComposer.Modal_Goal(
            "New Goal",
            "The following form will help you to create a new goal.")

        self.client.views_open(trigger_id=trigger_id, view=view)
        return make_response("", 200)

    def new_goal(self, request_json_py):
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        AddGoal(request_json)

        return make_response("", 200)

    def update_goal_modal(self, goal_id, trigger_id, message_ts):
        request_json_py = {"active": "true", "doc_id": goal_id}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        goals_response = GetGoals(request_json)
        goals_json = goals_response.get_json()

        goal = Goal()
        # goal_doc_id = goals_json["0"]["doc_id"]

        goal.setup_goal(goals_json["0"])

        view = SlackComposer.Modal_Goal(
            "Update Goal",
            "The following form will help you to update a goal.", goal,
            goal_id, message_ts)

        self.client.views_open(trigger_id=trigger_id, view=view)

        return make_response("", 200)

    def update_goal(self, request_json_py, goal_uuid, message_ts):
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        UpdateGoal(request_json, goal_uuid)

        # Get goal
        request_json_py = {"doc_id": goal_uuid}
        request_json_str = json.dumps(request_json_py)
        request_json = json.loads(request_json_str)
        response = GetGoals(request_json)
        goal_json = response.get_json()

        # Update transaction on Slack
        self.client.chat_update(
            channel=self.SLACK_CHANNEL_ID,
            ts=message_ts,
            blocks=SlackComposer.Attachment_Goal(goal_json["0"]),
        )

        return make_response("", 200)

    def convert_view_to_data(self, payload):
        state_values = payload["view"]["state"]["values"]

        input_type = state_values[SlackComposer.inputId_type][
            SlackComposer.inputId_type]["selected_option"]["value"]
        input_spendingType = state_values[SlackComposer.inputId_spendingType][
            SlackComposer.inputId_spendingType]["selected_option"]["value"]
        input_start = state_values[SlackComposer.inputId_start][
            SlackComposer.inputId_start]["selected_date"]
        input_end = state_values[SlackComposer.inputId_end][
            SlackComposer.inputId_end]["selected_date"]

        input_count_limit = None
        input_value_limit = None

        if SlackComposer.inputId_count_limit in state_values:
            if state_values[SlackComposer.inputId_count_limit][
                    SlackComposer.inputId_count_limit]["value"] is not None:
                input_count_limit = int(
                    state_values[SlackComposer.inputId_count_limit][
                        SlackComposer.inputId_count_limit]["value"])
        if SlackComposer.inputId_value_limit in state_values:
            if state_values[SlackComposer.inputId_value_limit][
                    SlackComposer.inputId_value_limit]["value"] is not None:
                input_value_limit = int(
                    state_values[SlackComposer.inputId_value_limit][
                        SlackComposer.inputId_value_limit]["value"])

        input_category = BudgetCategory.Default.value
        input_merchant_based = False

        input_merchant_name = None
        input_merchant_code = None
        if (len(state_values[SlackComposer.inputId_merchant_based][
                SlackComposer.inputId_merchant_based]["selected_options"]) >
                0):
            input_merchant_based = bool(
                state_values[SlackComposer.inputId_merchant_based][
                    SlackComposer.inputId_merchant_based]["selected_options"]
                [0]["value"])

        if (SlackComposer.inputId_merchant_name in state_values):
            input_merchant_name = state_values[
                SlackComposer.inputId_merchant_name][
                    SlackComposer.inputId_merchant_name]["value"]

        if (SlackComposer.inputId_merchant_code in state_values):
            input_merchant_code = state_values[
                SlackComposer.inputId_merchant_code][
                    SlackComposer.inputId_merchant_code]["value"]

        if (SlackComposer.inputId_category in state_values):
            input_category = state_values[SlackComposer.inputId_category][
                SlackComposer.inputId_category]["selected_option"]["value"]

        # if (SlackComposer.inputId_count_limit in state_values):
        #     input_count_limit = state_values[SlackComposer.inputId_count_limit][SlackComposer.inputId_count_limit]["value"]

        # if (SlackComposer.inputId_value_limit in state_values):
        #     input_value_limit = state_values[SlackComposer.inputId_value_limit][SlackComposer.inputId_value_limit]["value"]

        request_json_py = {
            "goal_type": "{}".format(input_type),
            "start_date": "{}".format(input_start),
            "end_date": "{}".format(input_end),
            "active": True,
            "goal_details": {
                "spending_type": "{}".format(input_spendingType),
                "merchant_based": input_merchant_based,
                # "merchant": {
                #     "merchant": "{}".format(input_merchant_name),
                #     "merchant_code": "{}".format(input_merchant_code),
                # },
                # "value_limit": "{}".format(input_value_limit),
                # "count_limit": "{}".format(input_count_limit),
                "budget_category": {
                    "category": "{}".format(input_category)
                },
            },
        }

        if input_merchant_based is True:
            request_json_py["goal_details"]["merchant"] = {
                "merchant": "{}".format(input_merchant_name),
                "merchant_code": "{}".format(input_merchant_code),
            }

        if input_value_limit is not None:
            request_json_py["goal_details"]["value_limit"] = input_value_limit

        if input_count_limit is not None:
            request_json_py["goal_details"]["count_limit"] = input_count_limit

        return request_json_py

    def Avatar_SendToSlack(self, payload):
        # Switch to different slack messages
        if payload["type"] == "avatar_update":
            return self.AvatarUpdate_FormatMessage(payload["content"])
        elif payload["type"] == "avatar_report":
            # Load avatar date from DB
            data = AvatarReport_PrepareData()
            return self.AvatarUpdate_FormatMessage(data)
        else:
            print("failed - unknown payload type")
            return

    def AvatarUpdate_FormatMessage(self, data):
        # Only send avatar image if level changed or if report type
        imageUrl = None
        level = None
        if data["level_diff"] != 0 or "report" in data:
            if self.BUCKET_PATH is False:
                return
            level = str(data["level"])
            imageUrl = self.BUCKET_PATH.replace("{REPLACE_LEVEL}", level)
            # imageBlock = SlackComposer.MessageBlock_Image(imageUrl, level)

        # Load header and stats
        header = SlackComposer.AvatarUpdate_GetHeader(data)
        stats = SlackComposer.AvatarUpdate_GetStats(data)

        message_blocks = [
            SlackComposer.MessageBlock_Divider(),
            SlackComposer.MessageBlock_TextHeader(header),
            SlackComposer.MessageBlock_Text(stats)
        ]

        if imageUrl is not None:
            message_blocks.append(
                SlackComposer.MessageBlock_Image(imageUrl, level))

        parent_message = self.client.chat_postMessage(
            channel=self.SLACK_CHANNEL_ID, blocks=message_blocks)

        return make_response("", parent_message.status_code)
 def test_missing_text_warnings_chat_update(self):
     client = WebClient(base_url="http://localhost:8888",
                        token="xoxb-api_test")
     resp = client.chat_update(channel="C111", ts="111.222", blocks=[])
     self.assertIsNone(resp["error"])
示例#5
0
class Slack:
    def __init__(self, token, channel_name=None, channel_id=None):
        self.client = WebClient(token)
        self.channel_id = channel_id

        channels = self.client.conversations_list(
            types='public_channel,private_channel')

        if channel_name:
            for channel in channels['channels']:
                if channel['name'] == channel_name:
                    self.channel_id = channel['id']
                    break
        if not self.channel_id:
            self.channel_id = self.client.conversations_create(
                name=channel_name.lower(), is_private=True)['channel']['id']
            admins = [
                u['id'] for u in self.client.users_list()['members']
                if u.get('is_admin') or u.get('is_owner')
            ]
            self.client.conversations_invite(channel=self.channel_id,
                                             users=admins)

    def send_snippet(self,
                     title,
                     initial_comment,
                     code,
                     code_type='python',
                     thread_ts=None):
        return self.client.files_upload(
            channels=self.channel_id,
            title=title,
            initial_comment=initial_comment.replace('<br>', ''),
            content=code,
            filetype=code_type,
            thread_ts=thread_ts)['ts']

    def send_exception_snippet(self,
                               domain,
                               event,
                               code_type='python',
                               thread_ts=None):
        message = traceback.format_exc() + '\n\n\n' + dumps(event, indent=2)
        subject = 'Error occurred in ' + domain
        self.send_snippet(subject,
                          subject,
                          message,
                          code_type=code_type,
                          thread_ts=thread_ts)

    def send_raw_message(self, blocks, thread_ts=None):
        return self.client.chat_postMessage(channel=self.channel_id,
                                            blocks=blocks,
                                            thread_ts=thread_ts)['ts']

    def update_raw_message(self, ts, blocks):
        self.client.chat_update(channel=self.channel_id, blocks=blocks, ts=ts)

    def get_perm_link(self, ts):
        return self.client.chat_getPermalink(channel=self.channel_id,
                                             message_ts=ts)['permalink']

    def send_message(self, message, attachment=None, thread_ts=None):
        blocks = [{
            'type': 'section',
            'text': {
                'type': 'mrkdwn',
                'text': message.replace('<br>', '')
            }
        }, {
            'type': 'divider'
        }]
        if attachment:
            blocks[0]['accessory'] = {
                'type': 'button',
                'text': {
                    'type': 'plain_text',
                    'text': attachment['text'],
                    'emoji': True
                },
                'url': attachment['value']
            }

        return self.send_raw_message(blocks, thread_ts)
示例#6
0
class SlackAction:
    client = None
    channel = None
    name = None
    details = None
    redis = None
    logger = None
    type = None

    def __init__(self, attack_details=None, update_message=None, redis=None):
        self.client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
        self.channel = os.environ["SLACK_BOT_CHANNEL"]
        self.name = os.getenv("SLACK_BOT_NAME", "FastNetMon")
        if attack_details:
            self.details = attack_details["details"]
            self.type = "attack"
        elif update_message:
            self.details = update_message
            self.type = "update"
        self.redis = redis
        self.logger = logging.getLogger(__name__)

    def _get_message_payload(
        self,
        message,
        mitigation_rules=None,
        packet_details=None,
        attack_details=None,
        thread_ts=None,
        fallback_message=None,
        actions=None,
    ):
        attachments = []
        blocks = message
        if actions:
            blocks = blocks + actions
        if attack_details is not None:
            attachments.append(attack_details)
        if mitigation_rules is not None:
            attachments = attachments + mitigation_rules
        if packet_details is not None:
            attachments.append(packet_details)
        return {
            "channel": self.channel,
            "username": self.name,
            "thread_ts": thread_ts,
            "icon_emoji": ":robot_face:",
            "fallback": fallback_message,
            "blocks": blocks,
            "attachments": attachments,
        }

    def _notify(self, message):
        try:
            # logger.warning(json.dumps(message, indent=4))
            attachments = message["attachments"]
            del message["attachments"]
            response = self.client.chat_postMessage(**message)
            assert response["message"]
            if message["thread_ts"] is None:
                message_thread_id = response["ts"]
            else:
                message_thread_id = message["thread_ts"]
            time.sleep(1)
            del message["blocks"]
            for attachment in attachments:
                message["attachments"] = [attachment]
                message["thread_ts"] = message_thread_id
                response = self.client.chat_postMessage(**message)
                assert response["message"]
                time.sleep(1)
            return message_thread_id
        except SlackApiError as e:
            # You will get a SlackApiError if "ok" is False
            assert e.response["ok"] is False
            assert e.response[
                "error"]  # str like 'invalid_auth', 'channel_not_found'
            self.logger.warning(f"Got an error: {e.response['error']}")
            self.logger.warning(json.dumps(message, indent=4))

    def _build_attack_details_table(self):
        dataset = self.details["attack_details"]
        dataset = dict(sorted(dataset.items()))
        attack_summary_fields = []
        for field in dataset:
            if "traffic" in field:
                raw_value = self.details["attack_details"][field]
                value = format_bps(raw_value)
            else:
                value = str(self.details["attack_details"][field])
            if value == "":
                value = "<not set>"
            attack_summary_fields.append("_{field}:_ {value}".format(
                field=field, value=value))
        return "\n".join(attack_summary_fields)

    def _build_flowspec_details_table(self, rule):
        flowspec_details = []
        for field in rule:
            value = rule[field]
            if isinstance(value, list):
                value = ", ".join(str(x) for x in value)
            if value == "":
                value = "<not set>"
            flowspec_details.append("_{field}:_ {value}".format(field=field,
                                                                value=value))
        return "\n".join(flowspec_details)

    def _get_attack_details(self):
        fields = self._build_attack_details_table()
        return {
            "blocks": [
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": "*Attack Summary Details*"
                    },
                },
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": fields
                    }
                },
            ],
            "fallback":
            "Summary of attack volumetric data",
        }

    def _get_flowspec_blocks(self, rule):
        fields = self._build_flowspec_details_table(rule)
        return [
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": "*Flowspec Rules*"
                },
            },
            {
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": fields
                }
            },
        ]

    def process_message(self):
        if self.type == "attack":
            self.process_attack_message()
        elif self.type == "update":
            self.process_update_message()

    def process_update_message(self):
        if "message" in self.details:
            blocks = self.details["message"]["blocks"]
            new_blocks = []
            for block in blocks:
                if block["type"] != "actions":
                    new_blocks.append(block)
            try:
                # logger.warning(json.dumps(message, indent=4))
                response = self.client.chat_update(
                    channel=self.details["channel"]["id"],
                    ts=self.details["message"]["ts"],
                    text="Ban has been removed",
                    blocks=new_blocks,
                )
                assert response["message"]
                return response["ts"]
            except SlackApiError as e:
                # You will get a SlackApiError if "ok" is False
                assert e.response["ok"] is False
                assert e.response[
                    "error"]  # str like 'invalid_auth', 'channel_not_found'
                self.logger.warning(f"Got an error: {e.response['error']}")
                self.logger.warning(json.dumps(self.details, indent=4))

    def process_attack_message(self):
        if self.details["action"] == "ban" or self.details[
                "action"] == "partial_block":
            attack_description = (
                "*RTBH IP {ip_address}*: {attack_protocol} " +
                "{attack_direction} with {attack_severity} severity {attack_type} "
                + "attack").format(
                    ip_address=self.details["ip"],
                    attack_protocol=self.details["attack_details"]
                    ["attack_protocol"],
                    attack_direction=self.details["attack_details"]
                    ["attack_direction"],
                    attack_severity=self.details["attack_details"]
                    ["attack_severity"],
                    attack_type=self.details["attack_details"]["attack_type"],
                )
            flowspec_attachments = None
            redis_key = self.details["attack_details"]["attack_uuid"]
            if self.details["action"] == "partial_block":
                redis_key = "fs-{attack_direction}-{ip_address}".format(
                    attack_direction=self.details["attack_details"]
                    ["attack_direction"],
                    ip_address=self.details["ip"],
                )
                attack_description = (
                    "*Flow Mitigation for IP {ip_address}*: {attack_protocol} "
                    +
                    "{attack_direction} with {attack_severity} severity {attack_type} "
                    + "attack"
                ).format(
                    ip_address=self.details["ip"],
                    attack_protocol=self.details["attack_details"]
                    ["attack_protocol"],
                    attack_direction=self.details["attack_details"]
                    ["attack_direction"],
                    attack_severity=self.details["attack_details"]
                    ["attack_severity"],
                    attack_type=self.details["attack_details"]["attack_type"],
                )
                flowspec_attachments = []
                for rule in self.details["flow_spec_rules"]:
                    flowspec_details = {
                        "blocks": [{
                            "type": "section",
                            "text": {
                                "type": "mrkdwn",
                                "text": "*Flow Rules for the block*",
                            },
                        }],
                        "fallback":
                        "Flow rules for the attack",
                    }
                    flowspec_details["blocks"] = flowspec_details[
                        "blocks"] + self._get_flowspec_blocks(rule)
                    flowspec_attachments.append(flowspec_details)
            packet_capture_details = "Not available"
            if "packet_dump" in self.details:
                packet_capture_details = "```{packet_details}```".format(
                    packet_details="\n".join(self.details["packet_dump"]))

            attack_summary_block = [
                {
                    "type": "section",
                    "text": {
                        "type": "mrkdwn",
                        "text": attack_description
                    },
                },
                {
                    "type": "divider"
                },
                {
                    "type": "section",
                    "text": {
                        "type":
                        "mrkdwn",
                        "text":
                        "Violation reason is {violation} in {direction} direction"
                        .format(
                            violation=self.details["attack_details"]
                            ["attack_detection_threshold"],
                            direction=self.details["attack_details"]
                            ["attack_direction"],
                        ),
                    },
                },
            ]

            attack_data = self._get_attack_details()

            packet_details = {
                "blocks": [
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": "*Packet Capture Sample*"
                        },
                    },
                    {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": packet_capture_details
                        },
                    },
                ],
                "fallback":
                "Packets in the capture",
            }

            actions = [{
                "type":
                "actions",
                "elements": [
                    {
                        "type": "button",
                        "text": {
                            "type": "plain_text",
                            "text": "Remove block :lock:",
                            "emoji": True,
                        },
                        "value": self.details["attack_details"]["attack_uuid"],
                    },
                ],
            }]

            message_thread = self.redis.get(redis_key)
            if message_thread is not None:
                message_thread = message_thread.decode("utf-8")
                actions = None

            message = self._get_message_payload(
                message=attack_summary_block,
                attack_details=attack_data,
                packet_details=packet_details,
                mitigation_rules=flowspec_attachments,
                fallback_message=attack_description,
                actions=actions,
                thread_ts=message_thread,
            )

            message_thread = self._notify(message)
            self.redis.set(redis_key, message_thread)
            if self.details["action"] == "partial_block":
                self.redis.expire(redis_key, 1800)

        elif self.details["action"] == "unban":
            ban_id = self.details["attack_details"]["attack_uuid"]
            message_thread = self.redis.get(ban_id)
            if message_thread is not None:
                message_thread = message_thread.decode("utf-8")
            tz = timezone(os.getenv("TIMEZONE", "Australia/Sydney"))
            action_time = (datetime.utcnow().replace(
                tzinfo=timezone("utc")).astimezone(tz=tz))
            action_description = [{
                "type": "section",
                "text": {
                    "type":
                    "mrkdwn",
                    "text":
                    "*Ban removed* for {ip_address} at {datetime}".format(
                        ip_address=self.details["ip"],
                        datetime=action_time.strftime(
                            "%a %b %d %H:%M:%S %Z %Y"),
                    ),
                },
            }]
            message = self._get_message_payload(
                message=action_description,
                thread_ts=message_thread,
                fallback_message="Ban removed",
            )
            self._notify(message)
        else:
            self.logger.warn("Data for unknown action type {action}".format(
                action=self.details["action"]))