RaDark

This integration enables you to fetch incidents and manage your RaDark monitor from Cortex XSOAR.

Data Enrichment & Threat Intelligence · KELA RaDark

Details

IDRaDark
ProviderKELA
CategoryData Enrichment & Threat Intelligence
From Version6.0.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

This integration enables you to fetch incidents and manage your RaDark monitor from Cortex XSOAR.
This integration was integrated and tested with version 2 of RaDark

Configure RaDark in Cortex

Parameter Description Required
API Key API Key generated from RaDark by your user. True
First time fetching Start fetching incidents from the specified time. False
Trust any certificate (not secure)   False
Use system proxy settings   False
Monitor ID Set your monitor ID in RaDark. True
Fetch incidents   False
Incidents Fetch Interval   False
Incident type   False
Max incidents to fetch each fetching Maximum supported: 1000 False
Incident types to fetch Set which incident types to fetch from RaDark. True

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

radark-incident-get-items


Fetch all items for an incident by the given incident ID.

Base Command

radark-incident-get-items

Input

Argument Name Description Required
incident_id The unique ID of an incident that requires enrichment. Required

Context Output

Path Type Description
Radark.itemDetails.items.item_id string The ID of the item on RaDark.
Radark.itemDetails.items.email string The email of the item.
Radark.itemDetails.items.domain string The domain of the item.
Radark.itemDetails.items.password string The password of the item.
Radark.itemDetails.items.password_type string The password type of the item.
Radark.itemDetails.items.source string The source of the item.
Radark.itemDetails.items.service string The service of the item.
Radark.itemDetails.items.dump_post_date string The dump post date of the item.
Radark.itemDetails.items.compromised_website string The compromised website of the item.
Radark.itemDetails.items.bot_id string The bot ID of the item.
Radark.itemDetails.items.resource string The resource of the item.
Radark.itemDetails.items.country string The country of the item.
Radark.itemDetails.items.source_ip string The source IP of the item.
Radark.itemDetails.items.infection_type string The infection type of the item.
Radark.itemDetails.items.updated_date string The updated date of the item.
Radark.itemDetails.items.username string The username of the item.
Radark.itemDetails.items.additional_data string The additional data of the item.
Radark.itemDetails.items.price string The price of the item.
Radark.itemDetails.items.isp string The ISP of the item.
Radark.itemDetails.items.ip string The IP of the item.
Radark.itemDetails.items.hostname string The hostname of the item.
Radark.itemDetails.items.port string The port of the item.
Radark.itemDetails.items.technology string The technology of the item.
Radark.itemDetails.items.cve_details string The CVE details of the item.
Radark.itemDetails.items.details string The details of the item.
Radark.itemDetails.items.type string The type of the item.
Radark.itemDetails.items.description string The description of the item.
Radark.itemDetails.items.date string The date of the item.
Radark.itemDetails.items.bin string The bin of the item.
Radark.itemDetails.items.number string The number of the item.
Radark.itemDetails.items.tags string The tags of the item.
Radark.itemDetails.items.link string The link of the item.
Radark.itemDetails.items.context string The context of the item.
Radark.itemDetails.details string General details of the incident.

Command Example

!radark-incident-get-items incident_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Context Example

{
    "Radark": {
        "itemDetails": {
            "items": [
                {
                    "item_id": "<ITEM_ID>",
                    "email": "testa@test.com",
                    "domain": "test.com",
                    "password": "-",
                    "password_type": "-", 
                    "service": "-"
                }
            ],
          "details": "Incident contains 44 items. Full details can be found on \"items\" tab."
        }
    }
}

*Items are flexible (base on incident type).

Human Readable Output

No data found for item ID: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

radark-email-enrich


Search a specific email address to get all exposed leaked credentials collected by RaDark.

Base Command

radark-email-enrich

Input

Argument Name Description Required
email The email address tested for leaked credentials. Required

Context Output

Path Type Description
Radark.emailDetails.emails.email string The email to enrich from RaDark.
Radark.emailDetails.emails.domain string The domain of the email.
Radark.emailDetails.emails.password_type string The password type of the email.
Radark.emailDetails.emails.password string The password of the email.
Radark.emailDetails.emails.service string The service of the email.
Radark.emailDetails.emails.source string The source of the email.
Radark.emailDetails.emails.source string The posted date of the email.

Command Example

!radark-email-enrich email=testa@test.com

Context Example

{
    "Radark": {
        "emailDetails": {
            "emails": [
                {
                    "date": "2017-01-12T19:43:00Z",
                    "domain": "test.com",
                    "email": "testa@test.com",
                    "password": "-",
                    "password_type": "-",
                    "service": "-",
                    "source": "ss"
                }
            ]
        }
    }
}

Human Readable Output

Email Domain Password Type Password Service Source Date
testa@test.com test.com - - - ss 2017-01-12T19:43:00Z

radark-item-handle


Mark item as handled on RaDark.

Base Command

radark-item-handle

Input

Argument Name Description Required
item_id The unique ID of an item that should be marked as handled on RaDark. Required

Context Output

There is no context output for this command.

Command Example

``````

Human Readable Output

Item ID (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) marked as handled

radark-item-purchase


Request to purchase an item offered for sale on an automated store.

Base Command

radark-item-purchase

Input

Argument Name Description Required
item_id The unique ID of an item that should requires purchase. Required

Context Output

There is no context output for this command.

Command Example

!radark-item-purchase item_id=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Human Readable Output

Bot ID () marked for purchasing

Configuration parameters

  • api_key — API Key (required)
  • first_fetch — First time fetching
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • monitor_id — Monitor ID (required)
  • isFetch — Fetch incidents
  • incidentFetchInterval — Incidents Fetch Interval
  • incidentType — Incident type
  • max_fetch — Max incidents to fetch each fetching
  • incident_types — Incident types to fetch (required)

Commands (4)

  • radark-email-enrich

    Search a specific email address to get all exposed leaked credentials collected by RaDark.

  • radark-incident-get-items

    Fetch all items for an incident by the given incident ID.

  • radark-item-handle

    Mark item as handled on RaDark.

  • radark-item-purchase

    Request to purchase an item offered for sale on an automated store.

import demistomock as demisto  # noqa: F401
from CommonServerPython import *  # noqa: F401

"""IMPORTS"""
import json
import traceback
from typing import Any, cast

import urllib3

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

# Validators
MAX_INCIDENTS_TO_FETCH = 1000
SUPPORTED_SUB_TYPES = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 23, 24, 26]
SUPPORTED_SUB_TYPES_FOR_PURCHASE = [16, 18, 26]
# Mappers And Formatters
TYPE_MAPPER = {
    "leaked_credentials": "Leaked Credentials",
    "reports": "Intelligence Reports",
    "botnets": "Compromised Accounts",
    "network_vulnerabilities": "Network Vulnerabilities",
    "credit_cards": "Credit Cards",
    "hacking_discussions": "Hacking Discussions",
}
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
HACKING_DISCUSSIONS_LINES_SNIPPET = 5
# URLs
RADARK_URL = "https://radark2.ke-la.com"
BASE_URL = RADARK_URL + "/api"
INCIDENT_URL = RADARK_URL + "/#/incident?monitorId={MONITOR_ID}&feedPropertyId={incident_id}"
FETCH_ITEMS_API = "incidents/feedProperty/{incident_id}?apiToken={API_KEY}&monitor_id={MONITOR_ID}&include_original_text=true"
FETCH_INCIDENTS_API = "aggregations?monitor_id={MONITOR_ID}&apiToken={API_KEY}&limit={max_results}"
EMAIL_ENRICHMENT_API = "incidents/all?monitor_id={MONITOR_ID}&apiToken={API_KEY}"
INCIDENT_ACTION_API = "incidents/{item_id}/{action}?apiToken={API_KEY}"
FETCH_AN_ITEM_API = "monitor_id/{MONITOR_ID}/incidents/{item_id}?apiToken={API_KEY}"
MESSAGE_API = "messages/message?apiToken={API_KEY}"
MENTIONS_LIST_API = "messages/mentionsList?monitorId={MONITOR_ID}&apiToken={API_KEY}"
# Default values
DEFAULT_MAX_FETCH = 10
DEFAULT_INCIDENT_TYPES = [
    "Leaked Credentials",
    "Intelligence Reports",
    "Compromised Accounts",
    "Network Vulnerabilities",
    "Credit Cards",
    "Hacking Discussions",
]  # All types allowed
DEFAULT_FIRST_TIME_TO_FETCH = "45 days"
# Get from instance
API_KEY = demisto.params().get("api_key")
MONITOR_ID = demisto.params().get("monitor_id")

""" CLIENT CLASS """


class Client(BaseClient):
    def __init__(self, base_url: str, verify: bool, headers: dict[str, str], proxy: bool, api_key: str, monitor_id: str):
        super().__init__(base_url=base_url, verify=verify, headers=headers, proxy=proxy)
        self.api_key = api_key
        self.monitor_id = monitor_id

    def search_alerts(self, start_time: int | None, max_results: int, incident_types: list[str]):
        # Base filter
        params: dict[str, dict] = {
            "filters": {"and": {"start_date": start_time}},
            "sort": {"by": "incident_date", "order": "asc"},
        }

        # Add indices to fetch to the filter
        if len(incident_types) > 0:
            indices = []
            for incident_type in incident_types:
                identifier = get_type(incident_type)
                if identifier != "":
                    indices.append({"identifier": identifier})
            params["filters"]["and"]["indices"] = indices

        headers = {"Content-Type": "application/json"}
        max_results = str(max_results)
        try:
            return self._http_request(
                method="POST",
                url_suffix=FETCH_INCIDENTS_API.format(MONITOR_ID=self.monitor_id, API_KEY=self.api_key, max_results=max_results),
                json_data=params,
                headers=headers,
            )
        except Exception as e:
            return_error(str(e))

    def incident_get_items(self, incident_id: str) -> dict[str, Any] | None:
        try:
            return self._http_request(
                method="GET",
                url_suffix=FETCH_ITEMS_API.format(incident_id=incident_id, API_KEY=self.api_key, MONITOR_ID=self.monitor_id),
            )
        except Exception as e:
            return_error(str(e))
            return None

    def email_enrich_data(self, email: str) -> dict[str, Any] | None:
        # filter
        params = {
            "index": "leaked_credentials",
            "getAll": False,
            "filters": {"and": {"emails": [email], "indices": [{"identifier": "leaked_credentials"}]}},
        }
        headers = {"Content-Type": "application/json"}
        try:
            return self._http_request(
                method="POST",
                url_suffix=EMAIL_ENRICHMENT_API.format(MONITOR_ID=self.monitor_id, API_KEY=self.api_key),
                json_data=params,
                headers=headers,
            )
        except Exception as e:
            return_error(str(e))
            return None

    def action_on_item(self, item_id: str, action: str) -> dict[str, Any] | None:
        params = {"monitor_id": self.monitor_id, "value": True, "force": False}
        headers = {"Content-Type": "application/json"}
        try:
            return self._http_request(
                method="POST",
                url_suffix=INCIDENT_ACTION_API.format(item_id=item_id, action=action, API_KEY=self.api_key),
                json_data=params,
                headers=headers,
            )
        except Exception as e:
            return_error(str(e))
            return None

    def get_item(self, item_id: str) -> dict[str, Any] | None:
        try:
            return self._http_request(
                method="GET",
                url_suffix=FETCH_AN_ITEM_API.format(item_id=item_id, API_KEY=self.api_key, MONITOR_ID=self.monitor_id),
            )
        except Exception as e:
            return_error(str(e))
            return None

    def message_on_incident(self, message: dict[str, Any], room: dict[str, str]) -> dict[str, Any] | None:
        params = {"monitorId": self.monitor_id, "message": message, "room": room}
        headers = {"Content-Type": "application/json"}
        try:
            return self._http_request(
                method="POST", url_suffix=MESSAGE_API.format(API_KEY=self.api_key), json_data=params, headers=headers
            )
        except Exception as e:
            return_error(str(e))
            return None

    def get_mention_list(self) -> dict[str, Any] | None:
        try:
            return self._http_request(
                method="GET", url_suffix=MENTIONS_LIST_API.format(API_KEY=self.api_key, MONITOR_ID=self.monitor_id)
            )
        except Exception as e:
            return_error(str(e))
            return None


""" HELPER FUNCTIONS """


def parse_email_enrichment_markdown_table(data: dict):
    items = data["incidents"]
    table = []
    # Set headers. Important for order.
    headers = ["email", "domain", "password_type", "password", "service", "source", "date"]
    # Parse each item.
    for item in items:
        email = item.get("email", "")
        domain = item.get("domain", "")
        password_type = item.get("password_type", "")
        password = item.get("password", "")
        service = item.get("service", "")
        source = item.get("source", "")
        date = item.get("posted_date", "")
        row = {
            "email": email if email else "-",
            "domain": domain if domain else "-",
            "password_type": password_type if password_type else "-",
            "password": password if password else "-",
            "service": service if service else "-",
            "source": source if source else "-",
            "date": timestamp_to_datestring(date * 1000, DATE_FORMAT) if date else "-",
        }
        table.append(row)
    # Return the items as Markdown
    return table, tableToMarkdown(name="", t=table, headers=headers, headerTransform=lambda s: s.replace("_", " ").title())


def parse_incident_markdown_table(data: dict, incident_id: str):
    items = data.get("incidents", "")
    aggr = data.get("aggregation", "")
    if not aggr or not items or not isinstance(items, list) or len(items) < 1:
        raise Exception("RaDark Error: Response is missing!")
    aggr_type = aggr.get("type", "")
    sub_type = aggr.get("sub_type", "")
    if not aggr_type or not sub_type:
        raise Exception("RaDark Error: Response is missing!")
    if aggr_type == "reports":
        details = "The Intelligence report is available on your RaDark monitor."
        table = []
    elif aggr_type == "botnets":
        count = len(items)
        many_suffix = "s" if count > 1 else ""
        details = f'Incident contains {count} item{many_suffix}. Full details can be found on "items" tab.'
        table = parse_botnets_markdown_table(items, sub_type)
    elif aggr_type == "leaked_credentials":
        count = len(items)
        many_suffix = "s" if count > 1 else ""
        details = f'Incident contains {count} item{many_suffix}. Full details can be found on "items" tab.'
        table = parse_leaked_credentials_markdown_table(items, sub_type)
    elif aggr_type == "network_vulnerabilities":
        count = len(items)
        many_suffix = "s" if count > 1 else ""
        details = f'Incident contains {count} item{many_suffix}. Full details can be found on "items" tab.'
        table = parse_network_vulnerabilities_markdown_table(items, sub_type)
    elif aggr_type == "credit_cards":
        count = len(items)
        many_suffix = "s" if count > 1 else ""
        details = f'Incident contains {count} item{many_suffix}. Full details can be found on "items" tab.'
        table = parse_credit_cards_markdown_table(items, sub_type)
    elif aggr_type == "hacking_discussions":
        details = (
            "incident may contain textual context or screenshots that include the company's assets. "
            'More details can be found on "items" tab.'
        )
        table = parse_hacking_discussions_markdown_table(items, aggr)
    else:
        details = f"No data found for item ID: {incident_id}"
        table = []
    return table, details


def parse_leaked_credentials_markdown_table(items: list[dict[str, Any]], sub_type: int):
    table = []
    # Parse each item base on subtype
    for item in items:
        item_id = item.get("id", "")
        email = item.get("email", "")
        domain = item.get("domain", "")
        password = item.get("password", "")
        password_type = item.get("password_type", "")
        row = {
            "item_id": item_id if item_id else "-",
            "email": email if email else "-",
            "domain": domain if domain else "-",
            "password": password if password else "-",
            "password_type": password_type if password_type else "-",
        }
        if sub_type in [2, 23]:
            source = item.get("source", "")
            date = item.get("posted_date", "")
            row["source"] = source if source else "-"
            row["date"] = timestamp_to_datestring(date * 1000, DATE_FORMAT) if date else "-"
        elif sub_type in [3]:
            pass
        elif sub_type in [19]:
            date = item.get("dump_post_date", "")
            compromised_website = item.get("compromised_website", "")
            row["dump_post_date"] = timestamp_to_datestring(date * 1000, DATE_FORMAT) if date else "-"
            row["compromised_website"] = compromised_website if compromised_website else "-"
        table.append(row)
    return table


def parse_botnets_markdown_table(items: list[dict[str, Any]], sub_type: int):
    table = []
    # Parse each item base on subtype
    for item in items:
        item_id = item.get("id", "")
        bot_id = item.get("bot_id", "")
        resource = item.get("resource", "")
        country = item.get("country", "")
        updated_date = item.get("updated_date", "")
        row = {
            "item_id": item_id if item_id else "-",
            "bot_id": bot_id if bot_id else "-",
            "resource": resource if resource else "-",
            "country": country if country else "-",
            "updated_date": timestamp_to_datestring(updated_date * 1000, DATE_FORMAT) if updated_date else "-",
        }
        if sub_type in [16]:
            additional_data, username, password = extract_available_data_from_item(item)
            source_ip = item.get("source_ip", "")
            price = item.get("price", "")
            row["username"] = username if username else "-"
            row["password"] = password if password else "-"
            row["additional_data"] = additional_data
            row["source_ip"] = source_ip if source_ip else "-"
            row["price"] = str(price) + "$" if price else price
        elif sub_type in [17]:
            username = item.get("username", "")
            password = item.get("password", "")
            source_ip = item.get("source_ip", "")
            infection_type = item.get("infection_type", "")
            row["username"] = username if username else "-"
            row["password"] = password if password else "-"
            row["source_ip"] = source_ip if source_ip else "-"
            row["infection_type"] = infection_type if infection_type else "-"
        elif sub_type in [18]:
            additional_data, username, password = extract_available_data_from_item(item)
            isp = item.get("isp", "")
            infection_type = item.get("infection_type", "")
            price = item.get("price", "")
            row["username"] = username if username else "-"
            row["password"] = password if password else "-"
            row["additional_data"] = additional_data
            row["isp"] = isp if isp else "-"
            row["infection_type"] = infection_type if infection_type else "-"
            row["price"] = str(price) + "$" if price else price
        elif sub_type in [26]:
            additional_data, username, password = extract_available_data_from_item(item)
            tags = item.get("tags", "")
            price = item.get("price", "")
            row["username"] = username if username else "-"
            row["password"] = password if password else "-"
            row["tags"] = tags if tags else "-"
            row["price"] = str(price) + "$" if price else price
        table.append(row)
    return table


def parse_network_vulnerabilities_markdown_table(items: list[dict[str, Any]], sub_type: int):
    table = []
    # Parse each item base on subtype
    for item in items:
        item_id = item.get("id", "")
        hostname = item.get("hostname", "")
        row = {
            "item_id": item_id if item_id else "-",
            "hostname": hostname if hostname else "-",
        }
        if sub_type in [4, 5, 6, 7, 8, 14]:
            ip = item.get("ip", "")
            port = item.get("port", "")
            service = item.get("service", "")
            isp = item.get("isp", "")
            row["ip"] = ip if ip else "-"
            row["port"] = port if port else "-"
            row["service"] = service if service else "-"
            row["isp"] = isp if isp else "-"
        elif sub_type in [9]:
            technology = item.get("technology", "")
            cve_details = item.get("cve_details", "")
            row["technology"] = technology if technology else "-"
            row["cve_details"] = cve_details if cve_details else "-"
        elif sub_type in [10, 11]:
            nv_type = item.get("type", "")
            details = item.get("details", "")
            row["type"] = nv_type if nv_type else "-"
            row["details"] = details if details else "-"
        elif sub_type in [20]:
            ip = item.get("ip", "")
            isp = item.get("isp", "")
            description = item.get("description", "")
            row["ip"] = ip if ip else "-"
            row["isp"] = isp if isp else "-"
            row["description"] = description if description else "-"
        table.append(row)
    return table


def parse_credit_cards_markdown_table(items: list[dict[str, Any]], sub_type: int):
    table = []
    # Parse each item base on subtype
    # Include both 13 and 24
    for item in items:
        item_id = item.get("id", "")
        bin = item.get("bin", "")
        number = item.get("number", "")
        source = item.get("source", "")
        date = item.get("posted_date", "")
        row = {
            "item_id": item_id if item_id else "-",
            "bin": bin if bin else "-",
            "number": number if number else "-",
            "source": source if source else "-",
            "date": timestamp_to_datestring(date * 1000, DATE_FORMAT) if date else "-",
        }
        table.append(row)
    return table


def parse_hacking_discussions_markdown_table(items: list[dict[str, Any]], aggr: dict[str, Any]):
    table = []
    aggr_media = aggr.get("media", [])
    aggr_tags = aggr.get("tags", [])
    if not isinstance(aggr_media, list) or not isinstance(aggr_tags, list):
        raise Exception("RaDark Error: Response is missing!")
    aggr_tags = [t.lower() for t in aggr_tags]
    # Image items handler
    for media_item in aggr_media:
        if not isinstance(media_item, dict):
            raise Exception("RaDark Error: Incorrect media_item type!")
        media_item_tags = media_item.get("tags", [])
        if not isinstance(media_item_tags, list):
            raise Exception("RaDark Error: Incorrect media_item_tags type!")
        for tag in media_item_tags:
            if not isinstance(tag, str):
                raise Exception("RaDark Error: Incorrect tag type!")
        if media_item_tags:
            media_item_url = media_item.get("url", "")
            if not isinstance(media_item_url, str):
                raise Exception("RaDark Error: Incorrect media_item_url type!")
            item_type = "image"
            tags = ", ".join(media_item_tags)
            link = media_item_url
            row = {
                "type": item_type if item_type else "-",
                "tags": tags if tags else "-",
                "link": link if link else "-",
                "context": "-",
            }
            table.append(row)

    # Snippet items handler
    item = items[0]
    if not isinstance(item, dict):
        raise Exception("RaDark Error: Incorrect item type!")
    text = item.get("text", {})
    if not isinstance(text, str):
        raise Exception("RaDark Error: Incorrect text type!")
    lines = text.splitlines()
    for i in range(len(lines)):
        tag_catched = []
        for tag in aggr_tags:
            if tag in lines[i].lower():
                tag_catched.append(tag)
        if len(tag_catched) > 0:
            snippet_lines = []
            for j in range(
                max(0, i - HACKING_DISCUSSIONS_LINES_SNIPPET), min(len(lines), i + 1 + HACKING_DISCUSSIONS_LINES_SNIPPET)
            ):
                snippet_lines.append(lines[j])
            item_type = "text"
            tags = ", ".join(tag_catched)
            context = "\n".join(snippet_lines)
            row = {
                "type": item_type if item_type else "-",
                "tags": tags if tags else "-",
                "link": "-",
                "context": context if context else "-",
            }
            table.append(row)
    return table


def extract_available_data_from_item(item: dict) -> tuple[str, str, str]:
    additional_data = ""
    available_data = item.get("available_data", "")
    if not available_data:
        available_data = {"-": ""}
    for key in available_data:
        additional_data += key + "(" + available_data[key] + ")," if available_data[key] else key + ","
    if additional_data[-1] == ",":
        additional_data = additional_data[0:-1]
    username = available_data.get("username", "")
    if not username:
        username = available_data.get("login", "")
        if not username:
            username = available_data.get("email", "")
    password = available_data.get("password", "")
    return additional_data, username, password


def get_first_time_fetch(first_fetch: str) -> int | None:
    first_fetch_time = arg_to_datetime(
        arg=first_fetch if first_fetch else DEFAULT_FIRST_TIME_TO_FETCH, arg_name="First fetch time", required=True
    )
    return int(first_fetch_time.timestamp()) if first_fetch_time else None


def get_max_fetch(max_results: str) -> int:
    if max_results.isnumeric():
        max_fetch = int(max_results)
        if max_fetch < 0:
            max_fetch = DEFAULT_MAX_FETCH
    else:  # Default value
        max_fetch = DEFAULT_MAX_FETCH
    if not max_fetch or max_fetch > MAX_INCIDENTS_TO_FETCH:
        max_fetch = MAX_INCIDENTS_TO_FETCH
    return max_fetch


def get_type(incident_type: str) -> str:
    for key in TYPE_MAPPER:
        if TYPE_MAPPER[key] == incident_type:
            return key
    return ""


def get_name(incident_type: str) -> str:
    return TYPE_MAPPER.get(incident_type, "Unknown Incident Type")


""" COMMAND FUNCTIONS """


def test_module(client: Client, first_fetch_time: int | None) -> str:
    try:
        client.search_alerts(start_time=first_fetch_time, max_results=1, incident_types=DEFAULT_INCIDENT_TYPES)
    except DemistoException as e:
        if "Forbidden" in str(e):
            return "Authorization Error: make sure API Key is correctly set"
        else:
            raise e
    return "ok"


def fetch_incidents(
    client: Client, max_results: int, last_run: dict[str, int], first_fetch_time: int | None, incident_types: list[str]
) -> tuple[dict[str, int], list[dict]]:
    # Get the last fetch time, if exists
    last_fetch = last_run.get("last_fetch", None)
    if not last_fetch:  # if missing, use what provided via first_fetch_time
        last_fetch = first_fetch_time
    else:  # otherwise use the stored last fetch
        last_fetch = int(last_fetch)

    # for type checking, making sure that latest_created_time is int
    latest_created_time = cast(int, last_fetch)

    # Initialize an empty list of incidents to return
    # Each incident is a dict with a string as a key
    incidents: list[dict[str, Any]] = []

    # Fetch alerts from RaDark.
    alerts = client.search_alerts(start_time=last_fetch, max_results=max_results, incident_types=incident_types)["data"][
        "aggregations"
    ]

    for alert in alerts:
        # If no created_time set is as epoch (0). We use time in ms so we must
        # convert it from the HelloWorld API response
        incident_created_time = int(alert.get("incident_date", "0"))

        # to prevent duplicates, we are only adding incidents with creation_time > last fetched incident
        if last_fetch and incident_created_time <= last_fetch:
            continue

        # Prevent unsupported sub type to fetch incidents
        sub_type = alert["sub_type"]
        if sub_type in SUPPORTED_SUB_TYPES:
            alert["type_description"] = get_name(alert["type"])
            alert["incident_url"] = INCIDENT_URL.format(MONITOR_ID=client.monitor_id, incident_id=alert["feed_property_id"])
            alert.pop("title", None)

            # Add monitor ID to the incident.
            alert["monitor_id"] = client.monitor_id

            # Parse incident
            incident = {
                "name": get_name(alert["type"]),
                "occurred": timestamp_to_datestring(incident_created_time * 1000),  # timestamp in ms
                "rawJSON": json.dumps(alert),
            }
            incidents.append(incident)

        # Update last run if needed
        if incident_created_time > latest_created_time:
            latest_created_time = incident_created_time

    # Save the next_run as a dict with the last_fetch key to be stored
    next_run = {"last_fetch": latest_created_time}
    return next_run, incidents


def incident_get_items_command(client: Client, args: dict[str, Any]) -> CommandResults:
    try:
        incident_id = str(args.get("incident_id", ""))
        incident_data = client.incident_get_items(incident_id=incident_id)
        if (
            isinstance(incident_data, dict)
            and isinstance(incident_data.get("data"), dict)
            and isinstance(incident_data["data"].get("incidents"), list)
            and len(incident_data["data"]["incidents"]) > 0
        ):
            parsed_data, readable_output = parse_incident_markdown_table(incident_data["data"], incident_id)
        else:
            readable_output = f"No data found for item ID: {incident_id}."
            parsed_data = []
        return CommandResults(
            readable_output=readable_output,
            outputs_prefix="Radark.itemDetails",
            outputs_key_field="items",
            outputs={"items": parsed_data, "details": readable_output},
        )
    except Exception as e:
        raise Exception(f"RaDark Error: Failed to execute command.\nError:\n {e!s} {incident_data}")


def email_enrich_command(client: Client, args: dict[str, Any]) -> CommandResults:
    try:
        email = str(args.get("email", ""))
        email_data = client.email_enrich_data(email=email)
        if (
            isinstance(email_data, dict)
            and isinstance(email_data.get("data"), dict)
            and isinstance(email_data["data"].get("incidents"), list)
            and len(email_data["data"]["incidents"]) > 0
        ):
            parsed_data, readable_output = parse_email_enrichment_markdown_table(email_data["data"])
        else:
            readable_output = f"No data found for email: {email}."
            parsed_data = None
        return CommandResults(
            readable_output=readable_output,
            outputs_prefix="Radark.emailDetails",
            outputs_key_field="emails",
            outputs={"emails": parsed_data},
        )
    except Exception as e:
        raise Exception(f"RaDark Error: Failed to execute command.\nError:\n {e!s} {email}")


def item_handle_command(client: Client, args: dict[str, Any]) -> CommandResults:
    try:
        item_id = str(args.get("item_id", ""))
        action_res = client.action_on_item(item_id=item_id, action="handled")
        if isinstance(action_res, dict) and isinstance(action_res.get("data"), dict) and action_res["data"].get("value"):
            readable_output = f"Item ID ({item_id}) marked as handled."
            return CommandResults(readable_output=readable_output)
        else:
            raise Exception("RaDark Error: Mark item action failed!")
    except Exception as e:
        raise Exception(f"RaDark Error: Failed to execute command on {item_id}.\nError:\n {e!s}")


def item_purchase_command(client: Client, args: dict[str, Any]) -> CommandResults:
    try:
        item_id = str(args.get("item_id", ""))
        bot_id = ""
        room_id = ""

        # Get mentions list:
        mentions_list_res = client.get_mention_list()
        if (
            isinstance(mentions_list_res, dict)
            and isinstance(mentions_list_res.get("data", ""), list)
            and len(mentions_list_res["data"]) > 0
            and isinstance(mentions_list_res["data"][0], dict)
            and "id" in mentions_list_res["data"][0]
        ):
            mentions_list = mentions_list_res["data"]

            # Fetch some important item data.
            item_res = client.get_item(item_id=item_id)
            if isinstance(item_res, dict) and isinstance(item_res.get("data", ""), dict):
                # Prevent execution on unsupported sub types.
                if item_res["data"].get("sub_type", -1) not in SUPPORTED_SUB_TYPES_FOR_PURCHASE:
                    raise Exception("RaDark Error: Sub type not supported for purchasing!")

                # Extract bot ID and incident ID.
                incident_id = item_res["data"].get("feed_property_id", "")
                if not incident_id:
                    raise Exception("RaDark Error: Item ID was not found!")
                bot_id = item_res["data"].get("bot_id", "")
                if not bot_id:
                    raise Exception("RaDark Error: Bot ID was not found!")

                # Check if chat room already exists.
                incident_res = client.incident_get_items(incident_id=incident_id)
                if (
                    isinstance(incident_res, dict)
                    and isinstance(incident_res.get("data", ""), dict)
                    and isinstance(incident_res["data"].get("chat", ""), dict)
                    and isinstance(incident_res["data"]["chat"].get("room", ""), dict)
                ):
                    room_id = incident_res["data"]["chat"]["room"].get("id", "")

                # Send the action status.
                action_res = client.action_on_item(item_id=item_id, action="request")
                if (
                    isinstance(action_res, dict)
                    and isinstance(action_res.get("data", ""), dict)
                    and action_res["data"].get("value", "")
                ):
                    # Send the chat request.
                    message = {
                        "text": f"Hi <b>@KELA</b> , I would like to acquire further details about bot: {bot_id}",
                        "mentionsList": mentions_list,
                    }
                    room = {"itemId": incident_id, "itemType": "FEED_PROPERTY"}
                    if room_id:
                        room["id"] = room_id

                    # Send message.
                    message_res = client.message_on_incident(message=message, room=room)
                    if (
                        isinstance(message_res, dict)
                        and isinstance(message_res.get("data", ""), dict)
                        and message_res["data"].get("roomId", "")
                    ):
                        # readable_output = 'Item marked for purchasing'
                        readable_output = f"Bot ID ('{bot_id}') marked for purchasing."
                    else:
                        raise Exception("RaDark Error: Purchase message was not created!")
                else:
                    raise Exception("RaDark Error: Item was not marked for purchase!")
            else:
                readable_output = f"No data found for item ID: {item_id}"
        else:
            raise Exception("RaDark Error: Mentions list was not found!")
        return CommandResults(readable_output=readable_output)
    except Exception as e:
        raise Exception(f"RaDark Error: Failed to execute command on {item_id}.\nError:\n {e!s}")


""" MAIN FUNCTION """


def main() -> None:
    params = demisto.params()
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)

    # How much time before the first fetch to retrieve incidents
    first_fetch_timestamp = get_first_time_fetch(demisto.params().get("first_fetch"))

    demisto.debug(f"Command being called is {demisto.command()}")
    try:
        # Initialize Client
        client = Client(
            base_url=BASE_URL, verify=verify_certificate, headers={}, proxy=proxy, api_key=API_KEY, monitor_id=MONITOR_ID
        )

        # Run the requested command
        if demisto.command() == "test-module":
            return_results(test_module(client, first_fetch_timestamp))
        elif demisto.command() == "fetch-incidents":
            # Convert the argument to an int using helper function or set to MAX_INCIDENTS_TO_FETCH
            max_results = get_max_fetch(demisto.params().get("max_fetch"))
            next_run, incidents = fetch_incidents(
                client=client,
                max_results=max_results,
                last_run=demisto.getLastRun(),
                first_fetch_time=first_fetch_timestamp,
                incident_types=demisto.params().get("incident_types", DEFAULT_INCIDENT_TYPES),
            )
            # Set last run and create incidents in XSOAR
            demisto.setLastRun(next_run)
            demisto.incidents(incidents)
        elif demisto.command() == "radark-incident-get-items":
            return_results(incident_get_items_command(client, demisto.args()))
        elif demisto.command() == "radark-email-enrich":
            return_results(email_enrich_command(client, demisto.args()))
        elif demisto.command() == "radark-item-handle":
            return_results(item_handle_command(client, demisto.args()))
        elif demisto.command() == "radark-item-purchase":
            return_results(item_purchase_command(client, demisto.args()))

    # Log exceptions and return errors
    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{e!s}")


""" ENTRY POINT """


if __name__ in ("__main__", "__builtin__", "builtins"):
    main()