Dataminr Pulse ReGenAI

Dataminr Pulse's AI-powered, real-time intelligence integrates into Cortex XSOAR workflows for faster detection and response.

Data Enrichment & Threat Intelligence · Dataminr Pulse

Details

IDDataminr Pulse ReGenAI
ProviderDataminr
CategoryData Enrichment & Threat Intelligence
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Overview

Dataminr Pulse brings the most advanced AI-powered real-time intelligence into Cortex XSOAR, easily fitting into your workflows and enabling rapid identification and mitigation of emerging threats so you can deliver faster time to detection and response.

Swiftly Close The Loop From Insight to Response

Effectively detect, prioritize and manage risk to protect your physical and digital assets with the fastest real-time alerting to discover threats as they unfold.

Broad Global Threat Coverage at Unmatched AI Speed

Dataminr has been the global leader in AI for risk detection since 2009. Dataminr Pulse is relied on by two thirds of Fortune 100 companies to inform their physical and cybersecurity operations. Every day, the Dataminr multi-modal AI platform analyzes billions of public data inputs in 105 languages from over 500K global sources including the deep and dark web, enabling you to:

  • Gain real-time visibility into deep and dark web sources including markets, forums, paste sites, and ransomware group sites.
  • Get first notice on emerging attacks impacting your network and third parties affecting your business.
  • Detect risk at a global scale and track the emergence and global spread of vulnerabilities so you can proactively mitigate risk.
  • Identify new ransomware groups and track attacks as they happen, giving you visibility of attacks impacting your or third party risk perspective.

Pulse for Cyber Risk Key Use Cases

  • Cyber-Physical Convergence: Gain real-time intelligence on converged cyber and physical threats, including physical threats to IT and OT infrastructure, network and power outages, disasters, and emerging geopolitical risks.
  • Vulnerability Prioritization: Prioritize patching with visibility to the entire lifecycle of a vulnerability, from pre-CVE to exploitation, while surfacing relevant vulnerabilities in your infrastructure.
  • External Attack Intelligence: Mitigate risk by tracking threats to your company, subsidiaries, and 3rd parties across ransomware, APT groups, leaks, breaches, DDoS, defacement, and malware activity.
  • Digital Risk Detection: Get early warnings of risk to digital assets, including leaked credentials and data, account and domain impersonation, and mentions across the surface deep and dark web.

Accelerate and Enrich SOC Workflows

  • Accelerate, enrich and trigger triage with contextual intelligence
  • Activate playbooks
  • Improve incident investigation and response
  • Support analysis and threat hunting workflows
  • Determine threat identification, scoring and classification by type, severity and status

Use cases

  1. Alert Ingestion

    Fetches the Dataminr Alerts as an XSOAR Incident based on the configuration parameters. This will have three filters available in place.
    1) Watchlist Names
    2) Query
    3) Alert type (severity)

  2. Alert Enrichment

    Use playbook Retrieve Alerts For IOCs - Dataminr Pulse to enrich XSOAR incidents using Dataminr Alerts.

    • This playbook requires three parameters:
      1) Text to enrich
      2) Number of alerts to retrieve for each indicator
      3) A boolean to use configured watchlist names
    • This playbook will extract indicators from given text (default will be entire incident context).
    • After that it will retrieve alerts for each indicator and will store those alerts into context with key RetrievedDataminrAlerts.

Configure Dataminr Pulse - ReGenAI in Cortex

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Dataminr Pulse - ReGenAI.
  3. Click Add instance to create and configure a new integration instance.
Parameter Description Required
Client ID The Client ID required to authenticate to the service. True
Client Secret The Client Secret required to authenticate to the service. True
Watchlist Names Provide the watchlist names from which to fetch the alerts. If not provided, alerts will be fetched from all available watchlists on the platform. False
Query Terms to search within Dataminr Alerts. False
Alert Type Filters the incoming alerts with the provided alert type. Default All. False
Max Fetch The maximum number of alerts to fetch each time. If the value is greater than 100, it will be considered as 100. The maximum is 100. False
Source Reliability Reliability of the source providing the intelligence data. False
Create relationships Create relationships between indicators as part of enrichment. False
Trust any certificate (not secure) Indicates whether to allow connections without verifying the SSL certificate’s validity. False
Use system proxy settings Indicates whether to use XSOAR’s system proxy settings to connect to the API. False
Incident type   False
Fetch incidents   False
Incidents Fetch Interval The incident fetch interval. False
First Fetch Time (not supported) This parameter is not supported as Dataminr Pulse API doesn’t have time based filtering for fetching of alerts. False
  1. Click Test to validate the URLs, token, and connection.

(Optional) Set up Google Maps in Cortex XSOAR to Display Alert Locations in the Incident Layout

  1. In Google Cloud Platform, do the following:

    • Create a Google Cloud Project.
    • Enable APIs and Services (API & Services>Dashboard> ENABLE APIS AND SERVICES).
    • Enable Maps JavaScript API.
    • Create the Maps JavaScript API key ( Credentials> CREATE CREDENTIALS>API key).
    • Copy the Maps JavaScript API key.
  2. Add the Maps JavaScript API key to Cortex XSOAR.

    • For XSOAR 6: Select Settings > ABOUT > Troubleshooting > Add Server Configuration.
      For XSOAR 8: Select Settings & Info > Settings > Server Settings > Add Server Configuration.
    • Add the following key and value:

      Key Value
      ui.google.api.key <Maps JavaScript API key>
    • Click Save.

Note

  1. If you detach the out-of-the-box mapper and make changes to it, the pack does not automatically get updates.
    • If you are using a custom incident type, you also need to create custom corresponding incoming mappers.

Troubleshooting

Known Issue: Custom CVE Indicators being overridden by the default CVE Type in XSOAR 8

We created a custom indicator type “Dataminr Pulse Vulnerability Indicator” similar to “CVE” so we can show the additional fields in our customized layout.
These indicators are extracted from Dataminr Pulse ReGenAI Alerts, but some of them are still being assigned the default “CVE” indicator type instead of our custom “Dataminr Pulse Vulnerability Indicator”.

Tips for Handling the Issue

Manually edit the indicators type from CVE to Dataminr Pulse Vulnerability Indicator that were enriched by Dataminr Pulse ReGenAI Alert.

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.

dataminrpulse-watchlists-get


Retrieves the Watchlists configured on the Dataminr platform.

Base Command

dataminrpulse-watchlists-get

Input

There are no input arguments for this command.

Context Output

Path Type Description
DataminrPulse.WatchLists.id Number An unique identifier for an individual list. This ID is needed to retrieve alerts for a given list.
DataminrPulse.WatchLists.type String The type of list. Can be one of the Topic, Company, or Custom.
DataminrPulse.WatchLists.name String The name of list as specified in Dataminr platform.
DataminrPulse.WatchLists.subType String The sub type of list as specified in Dataminr platform.

Command example


#### Context Example

```json
{
    "DataminrPulse": {
        "WatchLists": [
            {
               "id": 1,
               "name": "Attack Vendor",
               "type": "TOPIC",
               "subType": "CYBER"
            },
            {
               "id": 2,
               "name": "Cyber-Physical",
               "type": "TOPIC",
               "subType": "VULNERABILITY"
            }
        ]
    }
}

Human Readable Output

Watchlists

Watchlist ID Watchlist Name Watchlist Type Watchlist Sub Type
1 Attack Vendor TOPIC CYBER
2 Cyber-Physical TOPIC VULNERABILITY

dataminrpulse-alerts-get


Retrieves the alerts as per the provided watchlist_ids or query or configured watchlist_names parameter in integration.

Note: The “from” and “to” arguments should not be included on the first execution, there will not be any “from” or “to” cursor to reference. Only subsequent calls should contain those parameters.

Base Command

dataminrpulse-alerts-get

Input

Argument Name Description Required
watchlist_ids Comma-separated set of watchlist IDs. Optional
query Terms to search within Dataminr Alerts. Optional
from It points to a cursor that you want any alerts after. Note that only one of “from” or “to” can be included per request. Optional
to It points to a cursor that you want any alerts before. Note that only one of “from” and “to” can be included per request. Optional
num Maximum number of alerts to return. 100 is maximum value. Default is 40. Optional
use_configured_watchlist_names A Boolean indicating that If user does not provide watchlist IDs then it should use configured watchlist names with query parameter. Possible values are: yes, no. Default is yes. Optional

Context Output

Path Type Description
DataminrPulse.Alerts.alertId String The unique identifier of the alert.
DataminrPulse.Alerts.alertTimestamp String The timestamp when the alert was generated.
DataminrPulse.Alerts.alertType.name String The type of alert.
DataminrPulse.Alerts.alertTopics.id String The topic ID associated with the alert.
DataminrPulse.Alerts.alertTopics.name String The topic name associated with the alert.
DataminrPulse.Alerts.alertCompanies.name String The company name is related to the alert.
DataminrPulse.Alerts.alertCompanies.ticker String The stock ticker symbol of the company.
DataminrPulse.Alerts.alertSectors.name String The sector name associated with the alert.
DataminrPulse.Alerts.headline String The main headline or summary of the alert.
DataminrPulse.Alerts.subHeadline.title String The subheadline title.
DataminrPulse.Alerts.subHeadline.content String The subheadline detailed content.
DataminrPulse.Alerts.publicPost.timestamp String The timestamp of the original public post.
DataminrPulse.Alerts.publicPost.href String The source URL of the public post.
DataminrPulse.Alerts.publicPost.text String The text of the public post.
DataminrPulse.Alerts.publicPost.channels String The channels or platforms where the post appeared.
DataminrPulse.Alerts.publicPost.media.type String The type of attached media.
DataminrPulse.Alerts.publicPost.media.href String The media hyperlink.
DataminrPulse.Alerts.publicPost.englishText String The english text of the public post.
DataminrPulse.Alerts.eventCorroboration.timestamp String The timestamp of the event corroboration.
DataminrPulse.Alerts.eventCorroboration.summary.title String The title of the event corroboration summary.
DataminrPulse.Alerts.eventCorroboration.summary.content String The content of the event corroboration summary.
DataminrPulse.Alerts.estimatedEventLocation.name String The estimated name or location of the event.
DataminrPulse.Alerts.estimatedEventLocation.coordinates Number The coordinates of the estimated event location.
DataminrPulse.Alerts.estimatedEventLocation.probabilityRadius Number The probability radius of the estimated location.
DataminrPulse.Alerts.assetsMatched.locationAssets.name String The name of the customer location asset.
DataminrPulse.Alerts.assetsMatched.locationAssets.lng Number The longitude of the asset.
DataminrPulse.Alerts.assetsMatched.locationAssets.lat Number The latitude of the asset.
DataminrPulse.Alerts.assetsMatched.locationAssets.distanceFromEventLocation Number The distance between the asset and the alert’s estimated event location.
DataminrPulse.Alerts.assetsMatched.locationAssets.locationGroups.name String The name of the location group.
DataminrPulse.Alerts.assetsMatched.thirdPartyAssets.name String The name of the third-party asset.
DataminrPulse.Alerts.assetsMatched.thirdPartyAssets.customerProvidedId String The customer-provided unique ID for the asset.
DataminrPulse.Alerts.assetsMatched.travelSegments.name String The name of the travel segment location or asset.
DataminrPulse.Alerts.assetsMatched.travelSegments.lng Number The longitude of the asset.
DataminrPulse.Alerts.assetsMatched.travelSegments.lat Number The latitude of the asset.
DataminrPulse.Alerts.assetsMatched.travelSegments.distanceFromEventLocation Number The distance between the asset and the alert’s estimated event location.
DataminrPulse.Alerts.assetsMatched.travelSegments.travelType String The category of travel segment, such as HOTEL or FLIGHT.
DataminrPulse.Alerts.intelAgents.summary.type String The type of the intelligence agent summary.
DataminrPulse.Alerts.intelAgents.summary.title String The title of the intelligence agent summary.
DataminrPulse.Alerts.intelAgents.summary.content String The content of the intelligence agent summary.
DataminrPulse.Alerts.intelAgents.version String The version of the intelligence agent.
DataminrPulse.Alerts.intelAgents.timestamp String The timestamp of the intelligence agent.
DataminrPulse.Alerts.intelAgents.discoveredEntities.name String The name of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.type String The type of discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.summary String The summary of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.publishedDate String The published date of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.products.productName String The product name of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.products.productVendor String The product vendor of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.products.productVersion String The product version of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.knownExploitedDate String The known exploited date of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.epssScore String The EPSS score of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.cvss String The CVSS score of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.exploitable String The exploitability of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.exploitPocLinks String The exploit proof of concept links of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.affectedOperatingSystems String The affected operating systems of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.yaraRules String The YARA rules of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.aliases String The aliases of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.topLevelTechniqueName String The top-level technique name of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.techniqueName String The technique name of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.techniqueId String The technique ID of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.ttps.tacticName String The tactic name of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.countryOfOrigin String The country of origin of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.threatActors String The threat actors related to malware of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.websiteUrl String The website URL associated with the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.locations.address String The address of the location associated with the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.industry String The industry in which the discovered entity operates.
DataminrPulse.Alerts.intelAgents.discoveredEntities.primaryLocation.address String The primary address of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.organizationType String The type of organization associated with the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.headOfOrganization String The individual who heads the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.executives String The executives associated with the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.operatingRegions String The regions in which the discovered entity operates.
DataminrPulse.Alerts.intelAgents.discoveredEntities.address String The address information of the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.ownedBy String The entity or individual that owns the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.operatedBy String The entity or individual that operates the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.country String The country in which the discovered entity is located.
DataminrPulse.Alerts.intelAgents.discoveredEntities.timezone.utcOffsetSeconds Number The UTC offset in seconds for the entity’s timezone.
DataminrPulse.Alerts.intelAgents.discoveredEntities.timezone.displayName String The display name of the entity’s timezone.
DataminrPulse.Alerts.intelAgents.discoveredEntities.stateOrProvince String The state or province where the discovered entity is located.
DataminrPulse.Alerts.intelAgents.discoveredEntities.population String The population associated with the discovered entity’s location.
DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.issuingCountry String The country issuing the travel advisory.
DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.issuedDate String The date when the travel advisory was issued.
DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.issuedCountry String The country for which the travel advisory was issued.
DataminrPulse.Alerts.intelAgents.discoveredEntities.travelAdvisories.advisoryText String The text content of the travel advisory.
DataminrPulse.Alerts.intelAgents.discoveredEntities.president String The president associated with the discovered entity’s country or organization.
DataminrPulse.Alerts.intelAgents.discoveredEntities.officialLanguages String The official languages spoken in the entity’s location.
DataminrPulse.Alerts.intelAgents.discoveredEntities.capital String The capital city associated with the discovered entity’s country.
DataminrPulse.Alerts.intelAgents.discoveredEntities.locationType String The type of location represented by the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.managedBy String The entity or individual responsible for managing the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.nationalities String The nationalities associated with the discovered entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.pastRoles.tenureStartDate String The start date of the entity’s past role.
DataminrPulse.Alerts.intelAgents.discoveredEntities.pastRoles.tenureEndDate String The end date of the entity’s past role.
DataminrPulse.Alerts.intelAgents.discoveredEntities.pastRoles.roleName String The name of the past role associated with the entity.
DataminrPulse.Alerts.intelAgents.discoveredEntities.currentRoles.tenureStartDate String The start date of the entity’s current role.
DataminrPulse.Alerts.intelAgents.discoveredEntities.currentRoles.roleName String The name of the current role associated with the entity.
DataminrPulse.Alerts.liveBrief.summary String The live brief summary.
DataminrPulse.Alerts.liveBrief.version String The live brief version.
DataminrPulse.Alerts.liveBrief.timestamp String The live brief timestamp.
DataminrPulse.Alerts.dataminrAlertUrl String The Dataminr alert detail URL.
DataminrPulse.Alerts.alertReferenceTerms.text String The reference keywords or terms for the alert.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.id String The vulnerability ID.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.publishedDate String The published date of the vulnerability.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.knownExploitedDate String The known exploited date of the vulnerability.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.epssScore String The EPSS score of the vulnerability.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.cvss Number The CVSS score of the vulnerability.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.products.productName String The vulnerable product name.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.products.productVendor String The vulnerable product vendor.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.products.productVersion String The vulnerable product version.
DataminrPulse.Alerts.metadata.cyber.vulnerabilities.exploitPocLinks String The exploit PoC links for the vulnerability.
DataminrPulse.Alerts.metadata.cyber.URL.name String The related URL name.
DataminrPulse.Alerts.metadata.cyber.addresses.ip String The IP address involved.
DataminrPulse.Alerts.metadata.cyber.addresses.port Number The port number involved.
DataminrPulse.Alerts.metadata.cyber.addresses.version String The protocol or software version of the address.
DataminrPulse.Alerts.metadata.cyber.addresses.type String The type of the address.
DataminrPulse.Alerts.metadata.cyber.malware.name String The malware name.
DataminrPulse.Alerts.metadata.cyber.malware.affectedOperatingSystems String The affected operating systems.
DataminrPulse.Alerts.metadata.cyber.threatActors.name String The threat actor name.
DataminrPulse.Alerts.metadata.cyber.threatActors.aliases String The threat actor aliases.
DataminrPulse.Alerts.metadata.cyber.threatActors.countriesOfOrigin String The countries of origin of the threat actor.
DataminrPulse.Alerts.metadata.cyber.asOrgs.asn String The autonomous system number.
DataminrPulse.Alerts.metadata.cyber.asOrgs.asOrg String The autonomous system organization.
DataminrPulse.Alerts.metadata.cyber.hashValues.value String The hash value.
DataminrPulse.Alerts.metadata.cyber.hashValues.type String The hash type.
DataminrPulse.Alerts.listsMatched.id String The matched list ID.
DataminrPulse.Alerts.listsMatched.name String The matched list name.
DataminrPulse.Alerts.listsMatched.subType String The subtype of the matched list.
DataminrPulse.Alerts.listsMatched.topicIds String The topic IDs of the matched list.
DataminrPulse.Alerts.linkedAlerts.count Number The count of linked alerts.
DataminrPulse.Alerts.linkedAlerts.parentAlertId String The parent alert ID of the linked alert.
DataminrPulse.Cursor.from String The “from” points to a cursor that specifies the alerts you want after it.
DataminrPulse.Cursor.to String The “to” points to a cursor that specifies the alerts you want before it.

Command example

!dataminrpulse-alerts-get num=1

Context Example

{
    "DataminrPulse": {
        "Alerts": [
            {
                "alertId": "DUMMY_ALERT_ID",
                "alertTimestamp": "2025-07-07T19:19:00.397Z",
                "alertType": {
                    "name": "Alert"
                },
                "alertTopics": [
                    {
                        "id": "DUMMY_TOPIC_ID",
                        "name": "DUMMY_TOPIC_NAME"
                    }
                ],
                "alertCompanies": [
                    {
                        "name": "DUMMY_COMPANY",
                        "ticker": "DUMMY_TICKER"
                    }
                ],
                "alertSectors": [
                    {
                        "name": "DUMMY_SECTOR"
                    }
                ],
                "headline": "Spike detected in discussion related to threat actor DUMMY_ACTOR.",
                "subHeadline": {
                    "title": "DUMMY_TITLE",
                    "content": [
                        "DUMMY_CONTENT"
                    ]
                },
                "publicPost": {
                    "timestamp": "2025-01-01T00:00:00.000Z",
                    "href": "DUMMY_URL",
                    "channels": [
                        "DUMMY_CHANNEL"
                    ],
                    "media": [
                        {
                            "type": "photo",
                            "href": "DUMMY_IMAGE_URL"
                        }
                    ]
                },
                "estimatedEventLocation": {
                    "name": "DUMMY_LOCATION",
                    "coordinates": [
                        0,
                        0
                    ],
                    "probabilityRadius": 0
                },
                "intelAgents": [
                    {
                        "summary": [
                            {
                                "type": [
                                    "CYBER"
                                ],
                                "title": "Background Information",
                                "content": [
                                    "This is a placeholder description for background information related to the issue."
                                ]
                            },
                            {
                                "type": [
                                    "CYBER"
                                ],
                                "title": "Current Status",
                                "content": [
                                    "This is a placeholder description for the current status of the issue."
                                ]
                            },
                            {
                                "type": [
                                    "CYBER"
                                ],
                                "title": "Impact",
                                "content": [
                                    "This is a placeholder description for the potential impact of the issue."
                                ]
                            }
                        ],
                        "version": "prior",
                        "timestamp": "2025-01-01T00:00:00.000Z",
                        "discoveredEntities": [
                            {
                                "name": "DUMMY_ENTITY",
                                "type": "threatActor",
                                "aliases": [
                                    "DUMMY_ALIAS"
                                ]
                            },
                            {
                                "name": "DUMMY_ENTITY02",
                                "type": "malware",
                                "affectedOperatingSystems": [
                                    "DUMMY_OS"
                                ]
                            },
                            {
                                "name": "DUMMY_ENTITY03",
                                "type": "vulnerability",
                                "publishedDate": "2025-01-01T00:00:00.000Z",
                                "epssScore": 2.0,
                                "cvss": 2.5,
                                "products": [
                                    {
                                        "productName": "DUMMY_PRODUCT",
                                        "productVendor": "DUMMY_VENDOR",
                                        "productVersion": "DUMMY_VERSION"
                                    }
                                ],
                                "exploitPocLinks": [
                                    "DUMMY_LINK"
                                ]
                            }
                        ]
                    }
                ],
                "liveBrief": [
                    {
                        "summary": "DUMMY_LIVEBRIEF",
                        "version": "prior",
                        "timestamp": "2025-01-01T00:00:00.000Z"
                    }
                ],
                "dataminrAlertUrl": "https://app.dataminr.com/#alertDetail/DUMMY",
                "alertReferenceTerms": [
                    {
                        "text": "DUMMY_REF_TERM"
                    }
                ],
                "metadata": {
                    "cyber": {
                        "vulnerabilities": [
                            {
                                "id": "DUMMY_VULN",
                                "publishedDate": "2025-01-01T00:00:00.000Z",
                                "epssScore": 2.0,
                                "cvss": 2.5,
                                "products": [
                                    {
                                        "productName": "DUMMY_PRODUCT",
                                        "productVendor": "DUMMY_VENDOR",
                                        "productVersion": "DUMMY_VERSION"
                                    }
                                ],
                                "exploitPocLinks": [
                                    "DUMMY_LINK"
                                ]
                            }
                        ],
                        "URL": [
                            {
                                "name": "DUMMY_URL"
                            }
                        ],
                        "addresses": [
                            {
                                "ip": "0.0.0.0",
                                "port": 22,
                                "version": "DUMMY_VERSION"
                            }
                        ],
                        "malware": [
                            {
                                "name": "DUMMY_MALWARE"
                            }
                        ],
                        "threatActors": [
                            {
                                "name": "DUMMY_ACTOR"
                            }
                        ],
                        "asOrgs": [
                            {
                                "asn": "DUMMY_ASN",
                                "asOrg": "DUMMY_ORG"
                            }
                        ],
                        "hashValues": [
                            {
                                "value": "DUMMY_HASH",
                                "type": "DUMMY_HASH_TYPE"
                            }
                        ]
                    }
                },
                "listsMatched": [
                    {
                        "id": 3,
                        "name": "Data Security",
                        "subType": "CYBER",
                        "topicIds": [
                            "DUMMY_TOPIC_ID"
                        ]
                    }
                ],
                "linkedAlerts": [
                    {
                        "count": 4,
                        "parentAlertId": "DUMMY_PARENT_ID"
                    }
                ]
            }
        ],
        "Cursor": {
            "from": "DUMMY_CURSOR02",
            "to": "DUMMY_CURSOR01"
        }
    }
}

Human Readable Output

Alerts

Alert Type Alert ID Alert Name Intel Agents Summary Intel Agents Discovered Entities Live Brief Watchlist Name Alert Time Alert Location Post Link Alert Topics
Alert DUMMY_ALERT_ID Spike detected in discussion related to threat actor DUMMY_ACTOR. - type: CYBER
title: Background Information
content: This is a placeholder description for background information related to the issue.
- type: CYBER
title: Current Status
content: This is a placeholder description for the current status of the issue.
- type: CYBER
title: Impact
content: This is a placeholder description for the potential impact of the issue.
- name: DUMMY_ENTITY
type: threatActor
aliases: DUMMY_ALIAS
- name: DUMMY_ENTITY02
type: malware
affectedOperatingSystems: DUMMY_OS
- name: DUMMY_ENTITY03
type: vulnerability
publishedDate: 2025-01-01T00:00:00.000Z
epssScore: 2.0
cvss: 2.5
products:
- productName: DUMMY_PRODUCT
productVendor: DUMMY_VENDOR
productVersion: DUMMY_VERSION
exploitPocLinks: DUMMY_LINK
- summary: DUMMY_LIVEBRIEF
version: prior
timestamp: 2025-01-01T00:00:00.000Z
Data Security 2025-07-07T19:19:00.397Z DUMMY_LOCATION DUMMY_URL - id: DUMMY_TOPIC_ID
name: DUMMY_TOPIC_NAME

Cursor for pagination

from to
DUMMY_CURSOR02 DUMMY_CURSOR01

Migration Guide

Migrated Commands

Below is the list of commands that have been migrated from the “Dataminr Pulse” integration to the “Dataminr Pulse - ReGenAI” integration.

  • dataminrpulse-alerts-get
  • dataminrpulse-watchlists-get

Deprecated Commands

The following command from the previous integration has been deprecated from the Dataminr Pulse API side with no replacement.

  • dataminrpulse-related-alerts-get

Configuration parameters

  • credentials — Client ID (required)
  • watchlist_names — Watchlist Names
  • query — Query
  • alert_type — Alert Type
  • max_fetch — Max Fetch
  • integrationReliability — Source Reliability
  • create_relationships — Create relationships
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • incidentType — Incident type
  • isFetch — Fetch incidents
  • incidentFetchInterval — Incidents Fetch Interval
  • first_fetch — First Fetch Time (not supported)

Commands (5)

  • dataminrpulse-alerts-get

    Retrieves the alerts as per the provided watchlist_ids or query or configured watchlist_names parameter in integration. Note: The "from" and "to" arguments should not be included on the first execution, there will not be any "from" or "to" cursor to reference. Only subsequent calls should contain those parameters.

  • dataminrpulse-malware-enrich

    Enrich the "Dataminr Pulse Malware Indicator" custom indicator with relevant data.

  • dataminrpulse-threat-actor-enrich

    Enrich the "Dataminr Pulse Threat Actor Indicator" custom indicator with relevant data.

  • dataminrpulse-vulnerability-enrich

    Enrich the "Dataminr Pulse Vulnerability Indicator" custom indicator with relevant data.

  • dataminrpulse-watchlists-get

    Retrieves the Watchlists configured on the Dataminr platform.

"""Dataminr Pulse - ReGenAI Integration for Cortex XSOAR (aka Demisto)."""

from functools import reduce
from operator import concat
from urllib.parse import urlparse, parse_qs
from collections.abc import Callable
from copy import deepcopy

import demistomock as demisto
import urllib3
from CommonServerPython import *  # noqa # pylint: disable=unused-wildcard-import
from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """

# Base URL for the Dataminr Pulse ReGenAI API.
BASE_URL = "https://api.dataminr.com"

ENDPOINTS = {
    # Authentication endpoint for the Dataminr Pulse API.
    "AUTH_ENDPOINT": "/auth/v1/token",
    # Watchlists endpoint for the Dataminr Pulse API.
    "WATCHLISTS_ENDPOINT": "/pulse/v1/lists",
    # Alerts endpoint for the Dataminr Pulse API.
    "ALERTS_ENDPOINT": "/pulse/v1/alerts",
}

DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR

# Date format for displaying date in human-readable format in war room.
HR_DATE_FORMAT = "%d %b %Y, %I:%M %p UTC"

APPLICATION_NAME = "palo_alto_cortex_xsoar"
OK_CODES = (200, 201, 401)
DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE = 40
MAX_NUMBER_OF_ALERTS_TO_RETRIEVE = 100
STATUS_LIST_TO_RETRY = (429, *(status_code for status_code in requests.status_codes._codes if status_code >= 500))  # type: ignore
EARLY_EXPIRY_TIME = 30000  # in milliseconds
DEFAULT_ALERT_TYPE = "All"
VENDOR_NAME = "DataminrPulse"
CUSTOM_OUTPUT_PREFIX = "DataminrPulse.{}"

ERRORS = {
    "INVALID_JSON_OBJECT": "Status code: {}. Failed to parse json object from response: {}.",
    "UNAUTHORIZED_REQUEST": "Status code: {}. Unauthorized request: {}.",
    "GENERAL_AUTH_ERROR": "Status code: {}. Error occurred while creating an authorization token. "
    "Please check the Client ID, Client Secret {}.",
    "NOT_MATCHED_WATCHLIST_NAMES": "No matching watchlist data was found for the {} watchlist names configured in the instance.",
    "INVALID_NUM": '{} is invalid value for "{}". Value should be greater than 0 and less than or equal to {}.',
    "ATMOST_ONE_ALLOWED": 'At most one argument, either "{}" or "{}" is allowed.',
    "JSON_DECODE": "Failed to parse '{}' JSON string.",
    "REQUIRED_ARG": "Required argument '{}' is missing.",
}

ALERT_TYPE_TO_INCIDENT_SEVERITY = {"Alert": 1, "Urgent": 3, "Flash": 4}
DEFAULT_RELIABILITY = "A - Completely reliable"

# Output prefix for the alerts.
OUTPUT_PREFIX_ALERTS = "DataminrPulse.Alerts"
# Output prefix for the lists.
OUTPUT_PREFIX_WATCHLISTS = "DataminrPulse.WatchLists"
# Output prefix for the cursor.
OUTPUT_PREFIX_CURSOR = "DataminrPulse.Cursor"

""" CLIENT CLASS """


class DataminrPulseReGenAIClient(BaseClient):
    """DataminrPulseClient class to interact with the Dataminr Pulse API."""

    def __init__(self, client_id: str = "", client_secret: str = "", verify: bool = False, proxy: bool = False):
        """
        Constructor for the DataminrPulseClient class.

        :type client_id: ``str``
        :param client_id: Client ID to be used for authentication.

        :type client_secret: ``str``
        :param client_secret: Client Secret to be used for authentication.

        :type verify: ``bool``
        :param verify: Whether the request should verify the SSL certificate.

        :type proxy: ``bool``
        :param proxy: Whether to run the integration using the system proxy.
        """
        self.client_id = client_id
        self.client_secret = client_secret
        self.base_url = BASE_URL
        super().__init__(base_url=self.base_url, verify=verify, proxy=proxy, headers={})

    def http_request(
        self,
        method,
        url_suffix,
        params=None,
        status_list_to_retry=STATUS_LIST_TO_RETRY,
        backoff_factor=30,
        retries=3,
        internal_retries=3,
        **kwargs,
    ) -> Optional[dict]:
        """
        Method to override private _http_request of BaseClient to handle specific status code.

        :type method: ``str``
        :param method: The HTTP method, for example: GET, POST, and so on.

        :type url_suffix: ``str``
        :param url_suffix: The API endpoint.

        :type params: ``dict``
        :param params: URL parameters to specify the query.

        :type status_list_to_retry: ``iterable``
        :param status_list_to_retry: A set of integer HTTP status codes that we should force a retry on.

        :type backoff_factor ``float``
        :param backoff_factor: A backoff factor to apply between attempts

        :type retries: ``int``
        :param retries: How many retries should be made in case of a failure.

        :type internal_retries: ``int``
        :param internal_retries: How many retries should be made in case of an auth failure.

        :return: Response dict.
        :rtype: ``Optional[dict]``
        """
        # Adds a valid authentication token to the headers.
        dma_token = self.get_dma_token(use_existing_token=True)
        headers = {
            "Authorization": f"Bearer {dma_token}",
            "X-Application-Name": APPLICATION_NAME,
        }
        self._headers.update(headers)
        res = self._http_request(
            method=method,
            url_suffix=url_suffix,
            params=params,
            status_list_to_retry=status_list_to_retry,
            backoff_factor=backoff_factor,
            retries=retries,
            resp_type="response",
            ok_codes=OK_CODES,
            **kwargs,
        )
        res_status_code = res.status_code

        try:
            json_data = res.json()
        except ValueError as exception:
            raise DemistoException(ERRORS["INVALID_JSON_OBJECT"].format(res_status_code, res.content), exception)

        # If the success response is received, then return it.
        if res_status_code in [200, 201]:
            return json_data

        # If authentication failure happens.
        if res_status_code in [401]:
            if internal_retries > 0:
                dma_token = self.get_dma_token(use_existing_token=False)
                headers = {
                    "Authorization": f"Bearer {dma_token}",
                    "X-Application-Name": APPLICATION_NAME,
                }
                self._headers.update(headers)
                internal_retries = internal_retries - 1
                return self.http_request(
                    method=method,
                    url_suffix=url_suffix,
                    params=params,
                    status_list_to_retry=status_list_to_retry,
                    backoff_factor=backoff_factor,
                    retries=retries,
                    internal_retries=internal_retries,
                )
            try:
                err_msg = ERRORS["UNAUTHORIZED_REQUEST"].format(res_status_code, str(res.json()))
            except ValueError:
                err_msg = ERRORS["UNAUTHORIZED_REQUEST"].format(res_status_code, str(res))
            raise DemistoException(err_msg)
        return None

    def get_dma_token(self, use_existing_token: bool = True) -> Optional[str]:
        """
        Get a DMA token that was previously created if it is still valid, else, generate a new authorization token from
        the client id, client secret and refresh token.

        :type use_existing_token ``bool``
        :param use_existing_token: Use existing token if it is still valid.

        :return: DMA token.
        :rtype: ``Optional[str]``
        """
        integration_context: dict = get_integration_context()
        previous_token: dict = integration_context.get("token", {})

        # Check if there is existing valid authorization token.
        if (
            previous_token.get("dmaToken")
            and use_existing_token
            and previous_token.get("expire") > datetime.now(timezone.utc).timestamp() * 1000  # type: ignore
        ):
            demisto.debug("Got authentication token from the integration context.")
            return previous_token.get("dmaToken")  # type: ignore

        demisto.debug("Trying to generate a new authentication token.")
        data = {"client_id": self.client_id, "client_secret": self.client_secret, "grant_type": "api_key"}

        headers = {
            "Content-Type": "application/x-www-form-urlencoded",
            "X-Application-Name": APPLICATION_NAME,
        }
        res = super()._http_request(
            method="POST",
            url_suffix=ENDPOINTS["AUTH_ENDPOINT"],
            resp_type="response",
            headers=headers,
            data=data,
            ok_codes=OK_CODES,
            status_list_to_retry=STATUS_LIST_TO_RETRY,
        )
        res_status_code = res.status_code

        try:
            res = res.json()
        except ValueError as exception:
            raise DemistoException(ERRORS["INVALID_JSON_OBJECT"].format(res_status_code, res.content), exception)

        if "errors" in res:
            raise DemistoException(ERRORS["GENERAL_AUTH_ERROR"].format(res_status_code, str(res)))
        if res.get("dmaToken"):
            expiry_time = res.get("expire", 0) - EARLY_EXPIRY_TIME
            demisto.debug(
                f"Setting the expiry time of the authentication token to {timestamp_to_datestring(expiry_time, is_utc=True)}."
            )
            new_token = {"dmaToken": res.get("dmaToken"), "expire": expiry_time}
            integration_context.update({"token": new_token})
            set_integration_context(integration_context)
            return res.get("dmaToken")
        return None

    def get_alerts(
        self,
        watchlist_ids: Optional[list] = None,
        query: Optional[str] = None,
        _from: Optional[str] = None,
        to: Optional[str] = None,
        page_size: int = DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE,
    ) -> Optional[dict]:
        """
        Retrieves the alerts stored on the Dataminr platform.

        :type watchlist_ids ``Optional[list]``
        :param watchlist_ids: List of watchlist id.

        :type query ``Optional[str]``
        :param query: Terms to search within Dataminr Alerts.

        :type _from ``Optional[str]``
        :param _from: Provide cursor value to get alerts after that.

        :type to ``Optional[str]``
        :param to: Provide cursor value to get alerts before that.

        :type page_size ``int``
        :param page_size: Maximum number of alerts to return.

        :return: A dictionary of alerts.
        :rtype: ``Optional[dict]``
        """
        params = {
            "pageSize": page_size,
            "from": _from,
            "to": to,
            "query": query,
        }
        remove_nulls_from_dictionary(params)
        if watchlist_ids:
            params["lists"] = ",".join(map(str, watchlist_ids))  # type: ignore
        return self.http_request(method="GET", url_suffix=ENDPOINTS["ALERTS_ENDPOINT"], params=params)

    def get_watchlists(self) -> Optional[dict]:
        """Retrieves the watchlists stored on the Dataminr platform.

        :return: A dictionary of Watchlists grouped by their types.
        :rtype: ``Optional[Dict]``
        """
        return self.http_request(method="GET", url_suffix=ENDPOINTS["WATCHLISTS_ENDPOINT"])


""" HELPER FUNCTIONS """


def validate_params_for_alerts_get(
    watchlist_ids: Optional[list] = None,
    watchlist_names: Optional[list] = None,
    page_size: int = DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE,
    use_configured_watchlist_names: bool = False,
    is_fetch: bool = False,
    _from: Optional[str] = None,
    to: Optional[str] = None,
):
    """
    To validate arguments for the alerts get.

    :type watchlist_ids ``Optional[List]``
    :param watchlist_ids: List of watchlist id.

    :type watchlist_names ``Optional[List]``
    :param watchlist_names: Watchlist names.

    :type page_size ``int``
    :param page_size: Maximum number of alerts to return.

    :type use_configured_watchlist_names ``bool``
    :param use_configured_watchlist_names: Use configured watchlist names.

    :type is_fetch ``bool``
    :param is_fetch: Function is called by fetch_incident method.

    :type _from ``Optional[str]``
    :param _from: Start cursor for the alerts.

    :type to ``Optional[str]``
    :param to: End cursor for the alerts.
    """
    if is_fetch and (page_size < 1 or page_size > MAX_NUMBER_OF_ALERTS_TO_RETRIEVE):
        raise ValueError(ERRORS["INVALID_NUM"].format(page_size, "Max Fetch", MAX_NUMBER_OF_ALERTS_TO_RETRIEVE))
    elif page_size < 1 or page_size > MAX_NUMBER_OF_ALERTS_TO_RETRIEVE:
        raise ValueError(ERRORS["INVALID_NUM"].format(page_size, "num", MAX_NUMBER_OF_ALERTS_TO_RETRIEVE))

    if (is_fetch or use_configured_watchlist_names) and (watchlist_names and not watchlist_ids):
        raise ValueError(ERRORS["NOT_MATCHED_WATCHLIST_NAMES"].format(watchlist_names))

    if _from and to:
        raise ValueError(ERRORS["ATMOST_ONE_ALLOWED"].format("from", "to"))


def transform_watchlists_data(watchlists_data: Optional[dict] = None) -> list:
    """
    Transform watchlist data from dictionary to single List.

    :type watchlists_data ``Optional[dict]``
    :param watchlists_data: Response to be converted in single list.

    :return: List of response.
    :rtype: ``list``
    """
    list_of_watchlists = watchlists_data.get("lists", {}).values()  # type: ignore
    # The returned object is a dictionary where the lists are grouped by their type.
    # So, modifying this object and creating a list of all list objects as the object itself contains
    # a property named "type" in it, which defines the type of the list.
    list_of_watchlists = list(list_of_watchlists)
    # The created list will be the list of lists. So, flattening the list is required.
    list_of_watchlists = reduce(concat, list_of_watchlists)
    return list_of_watchlists


def get_watchlist_ids(client: DataminrPulseReGenAIClient, watchlist_names: Optional[list] = None) -> list:
    """
    Get watchlist IDs as per the given watchlist names using integration context and get_watchlists method from client.

    :type client ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to get watchlists data.

    :type watchlist_names ``Optional[List]``
    :param watchlist_names: Watchlist names.

    :return: Watchlist IDs.
    :rtype: ``list``
    """
    watchlists_data: list = transform_watchlists_data(client.get_watchlists())
    watchlist_names_in_lower = [watchlist_name.lower() for watchlist_name in watchlist_names] if watchlist_names else []
    filtered_watchlists_data: list = (
        list(
            filter(
                lambda watchlist_data: watchlist_data.get("name").lower() in watchlist_names_in_lower,  # type: ignore
                watchlists_data,
            )
        )
        if watchlist_names
        else watchlists_data
    )
    if not filtered_watchlists_data:
        demisto.debug(
            f'No matching watchlist data was found for the "{watchlist_names}" watchlist names configured in the instance.'
        )
        return []
    watchlist_ids: list = [watchlist_data.get("id") for watchlist_data in filtered_watchlists_data]
    watchlist_ids: list = list(filter(None, watchlist_ids))
    return watchlist_ids


def create_media_html(alert: dict) -> str:
    """
    Create HTML content for the media.

    :type alert: ``dict``
    :param alert: Alert data.

    :rtype: ``str``
    :return: HTML content for the media.
    """
    media = alert.get("publicPost", {}).get("media", [])
    if not media:
        return ""

    photo_media = list(filter(lambda photo: photo.get("type") == "photo" or photo.get("type") == "image", media))
    if not photo_media:
        return ""

    html_content = []

    for photo in photo_media:
        html_content.append(f'<img src="{photo.get("href", "")}" alt="Photo">')

    media_html = "\n".join(html_content)
    return media_html


def remove_empty_elements_for_hr(d: Any) -> Any:
    """
    Recursively remove empty lists, empty dicts, or None elements from a dictionary or list.
    :param d: Input dictionary or list.
    :return: Dictionary or list with all empty lists, and empty dictionaries removed.
    """
    if not isinstance(d, dict | list):
        return str(d) if isinstance(d, int | float) else d
    elif isinstance(d, list):
        return [v for v in (remove_empty_elements_for_hr(v) for v in d) if not check_empty(v)]
    return {k: v for k, v in ((k, remove_empty_elements_for_hr(v)) for k, v in d.items()) if not check_empty(v)}


def remove_empty_elements_for_fetch(d: Any) -> Any:
    """
    Recursively remove empty lists, empty dicts, or None elements from a dictionary or list.
    :param d: Input dictionary or list.
    :return: Dictionary or list with all empty lists, and empty dictionaries removed.
    """
    if not isinstance(d, dict | list):
        return d
    elif isinstance(d, list):
        return [v for v in (remove_empty_elements_for_fetch(v) for v in d) if not check_empty(v)]
    return {k: v for k, v in ((k, remove_empty_elements_for_fetch(v)) for k, v in d.items()) if not check_empty(v)}


def check_empty(x: Any) -> bool:
    """
    Check if input is empty (None, empty dict, empty list, or empty string).

    :param x: Input to check.
    :type x: Any
    :return: True if x is empty, False otherwise.
    :rtype: bool
    """
    return x is None or x == {} or x == [] or x == ""


def trim_spaces_from_args(args: Dict) -> Dict:
    """Trim spaces from values of the args Dict.

    :type args: ``Dict``
    :param args: Dict to trim spaces from.

    :rtype: ``Dict``
    :return: Arguments after trim spaces.
    """
    for key, val in args.items():
        if isinstance(val, str):
            args[key] = val.strip()
        elif isinstance(val, dict):
            args[key] = trim_spaces_from_args(val)
        elif isinstance(val, list):
            for i in range(len(val)):
                if isinstance(val[i], str):
                    val[i] = val[i].strip()
                elif isinstance(val[i], dict):
                    val[i] = trim_spaces_from_args(val[i])

    return args


def prepare_hr_for_watchlists_get(watchlists: List) -> str:
    """
    Prepare human-readable string for war room entry.

    :type watchlists: ``List``
    :param watchlists: List of watchlists.

    :return: Human-readable output.
    :rtype: ``str``
    """
    # The title of the table.
    title = "Watchlists"
    # Data dictionary for the table.
    hr_outputs = [
        {
            "Watchlist ID": wl.get("id", ""),
            "Watchlist Name": wl.get("name", ""),
            "Watchlist Type": wl.get("type", ""),
            "Watchlist Sub Type": wl.get("subType", ""),
        }
        for wl in watchlists
    ]
    # Table headers.
    headers = ["Watchlist ID", "Watchlist Name", "Watchlist Type", "Watchlist Sub Type"]
    return tableToMarkdown(title, hr_outputs, headers, removeNull=True)


def prepare_hr_for_alerts(alerts: List) -> str:
    """Prepare Human Readable output for alerts.

    :type alerts: ``List``
    :param alerts: Response from the API.

    :rtype: ``str``
    :return: Human readable output.
    """
    # The title of the table.
    title = "Alerts"

    # This will store the data dictionaries for the table.
    hr_outputs = []

    for alert in alerts:
        # List of watchlist names matched in the alert.
        watchlist_names = [watchlist.get("name") for watchlist in alert.get("listsMatched", [])]
        intel_agents = deepcopy(alert.get("intelAgents", []))
        intel_agents_summary = []
        discovered_entities = []

        for intel_agent in intel_agents:
            intel_agents_summary.extend(intel_agent.get("summary", []))
            discovered_entities.extend(intel_agent.get("discoveredEntities", []))

        for summary in intel_agents_summary:
            summary["type"] = ", ".join(summary.get("type", []))
            summary["content"] = "\n".join(summary.get("content", []))

        for discovered_entity in discovered_entities:
            if discovered_entity.get("type") == "threatActor" and discovered_entity.get("aliases"):
                discovered_entity["aliases"] = ", ".join(discovered_entity.get("aliases", []))
            elif discovered_entity.get("type") == "malware" and discovered_entity.get("affectedOperatingSystems"):
                discovered_entity["affectedOperatingSystems"] = ", ".join(discovered_entity.get("affectedOperatingSystems", []))
            elif discovered_entity.get("type") == "vulnerability" and discovered_entity.get("exploitPocLinks"):
                discovered_entity["exploitPocLinks"] = ", ".join(discovered_entity.get("exploitPocLinks", []))

        hr_outputs.append(
            {
                "Alert Type": alert.get("alertType", {}).get("name", ""),
                "Alert ID": f"[{alert.get('alertId', '')}]({alert.get('dataminrAlertUrl', '')})",
                "Alert Name": alert.get("headline", ""),
                "Intel Agents Summary": intel_agents_summary if intel_agents_summary else "",
                "Intel Agents Discovered Entities": discovered_entities if discovered_entities else "",
                "Live Brief": alert.get("liveBrief", ""),
                "Watchlist Name": ", ".join(watchlist_names),
                "Alert Time": alert.get("alertTimestamp", ""),
                "Alert Location": alert.get("estimatedEventLocation", {}).get("name", ""),
                "Post Link": alert.get("publicPost", {}).get("href", ""),
                "Alert Topics": alert.get("alertTopics", ""),
            }
        )

    # Table headers.
    headers = [
        "Alert Type",
        "Alert ID",
        "Alert Name",
        "Intel Agents Summary",
        "Intel Agents Discovered Entities",
        "Live Brief",
        "Watchlist Name",
        "Alert Time",
        "Alert Location",
        "Post Link",
        "Alert Topics",
    ]
    return tableToMarkdown(
        title,
        hr_outputs,
        headers,
        removeNull=True,
        url_keys=["Post Link"],
        json_transform_mapping={
            "Alert Topics": JsonTransformer(is_nested=True),
            "Intel Agents Summary": JsonTransformer(is_nested=True),
            "Intel Agents Discovered Entities": JsonTransformer(is_nested=True),
            "Live Brief": JsonTransformer(is_nested=True),
        },
    )


def prepare_hr_for_cursor(cursor: Dict) -> str:
    """Prepare Human Readable output for cursor.

    :type cursor: ``Dict``
    :param cursor: Contains from and to parameter.

    :rtype: ``str``
    :return: Human readable output.
    """
    # The title of the table.
    title = "Cursor for pagination"

    # This will store the data dictionaries for the table.
    _from = cursor.get("from")
    if _from:
        _from = re.escape(_from)
    to = cursor.get("to")
    if to:
        to = re.escape(to)
    hr_outputs = [{"from": _from, "to": to}]

    # Table headers.
    headers = ["from", "to"]
    return tableToMarkdown(title, hr_outputs, headers, removeNull=True)


def create_vulnerability_indicators(alert: dict) -> list:
    """
    Create vulnerability indicators for the alert.

    :type alert: ``dict``
    :param alert: Alert data.

    :rtype: ``list``
    :return: List of vulnerability indicators.
    """
    vulnerability_intel_agents_list = []
    vulnerability_metadata_list = alert.get("metadata", {}).get("cyber", {}).get("vulnerabilities", [])
    intel_agents = alert.get("intelAgents", [])

    for vulnerability in vulnerability_metadata_list:
        if "epssScore" in vulnerability:
            vulnerability["epssScore"] = str(vulnerability.get("epssScore", "0")) + "%"

    for intel_agent in intel_agents:
        discovered_entities = intel_agent.get("discoveredEntities", [])
        for discovered_entity in discovered_entities:
            if discovered_entity.get("type", "") == "vulnerability":
                discovered_entity["id"] = discovered_entity.get("name", "")
                if "epssScore" in discovered_entity:
                    discovered_entity["epssScore"] = str(discovered_entity.get("epssScore", "0")) + "%"
                vulnerability_intel_agents_list.append(discovered_entity)

    merged = {}
    for vuln in vulnerability_intel_agents_list:
        vid = vuln.get("id")
        if vid is not None:
            merged[vid] = deepcopy(vuln)

    for vuln in vulnerability_metadata_list:
        vid = vuln.get("id")
        if vid is None:
            continue
        if vid in merged:
            merged[vid] = merge_dicts(merged[vid], vuln)
        else:
            merged[vid] = deepcopy(vuln)

    return list(merged.values())


def create_malware_indicators(alert: dict) -> list:
    """
    Create malware indicators for the alert.

    :type alert: ``dict``
    :param alert: Alert data.

    :rtype: ``list``
    :return: List of malware indicators.
    """
    malware_intel_agents_list = []
    malware_metadata_list = alert.get("metadata", {}).get("cyber", {}).get("malware", [])
    intel_agents = alert.get("intelAgents", [])

    for malware in malware_metadata_list:
        malware["name"] = "Malware: [" + malware.get("name", "") + "]"

    for intel_agent in intel_agents:
        discovered_entities = intel_agent.get("discoveredEntities", [])
        for discovered_entity in discovered_entities:
            if discovered_entity.get("type", "") == "malware":
                discovered_entity["name"] = "Malware: [" + discovered_entity.get("name", "") + "]"
                malware_intel_agents_list.append(discovered_entity)

    merged = {}
    for malware in malware_intel_agents_list:
        mid = malware.get("name")
        if mid is not None:
            merged[mid] = deepcopy(malware)

    for malware in malware_metadata_list:
        mid = malware.get("name")
        if mid is None:
            continue
        if mid in merged:
            merged[mid] = merge_dicts(merged[mid], malware)
        else:
            merged[mid] = deepcopy(malware)

    return list(merged.values())


def create_threat_actors_indicators(alert: dict) -> list:
    """
    Create threat actors indicators for the alert.

    :type alert: ``dict``
    :param alert: Alert data.

    :rtype: ``list``
    :return: List of threat actors indicators.
    """
    threat_actor_intel_agents_list = []
    threat_actor_metadata_list = alert.get("metadata", {}).get("cyber", {}).get("threatActors", [])
    intel_agents = alert.get("intelAgents", [])

    for threat_actor in threat_actor_metadata_list:
        threat_actor["name"] = "Threat Actor: [" + threat_actor.get("name", "") + "]"

    for intel_agent in intel_agents:
        discovered_entities = intel_agent.get("discoveredEntities", [])
        for discovered_entity in discovered_entities:
            if discovered_entity.get("type", "") == "threatActor":
                discovered_entity["name"] = "Threat Actor: [" + discovered_entity.get("name", "") + "]"
                threat_actor_intel_agents_list.append(discovered_entity)

    merged = {}
    for actor in threat_actor_intel_agents_list:
        name = actor.get("name")
        if name:
            merged[name] = deepcopy(actor)

    for actor in threat_actor_metadata_list:
        name = actor.get("name")
        if not name:
            continue
        if name in merged:
            merged[name] = merge_dicts(merged[name], actor)
        else:
            merged[name] = deepcopy(actor)

    return list(merged.values())


def create_flight_data(alert: dict) -> dict:
    """
    Create flight data for the alert.

    :type alert: ``dict``
    :param alert: Alert data.

    :rtype: ``dict``
    :return: Flight data.
    """
    sub_headline_content = alert.get("subHeadline", {}).get("content", [])
    flight_data = {}

    for item in sub_headline_content:
        # Skip empty or whitespace-only lines
        if not item.strip():
            continue

        # Check if it contains a colon (valid key-value format)
        if ":" in item:
            key, value = item.split(":", 1)
            key, value = key.strip(), value.strip()

            # Only add if both key and value are non-empty
            if key and value:
                flight_data[key] = value

    return flight_data


def calculate_dbot_score(cvss: float) -> int:
    if cvss >= 4.0:
        return Common.DBotScore.BAD
    elif cvss > 0.0:
        return Common.DBotScore.SUSPICIOUS
    else:
        return Common.DBotScore.NONE


def merge_dicts(d1: dict, d2: dict) -> dict:
    """
    Merge two dictionaries recursively and handle duplicate values.
    Works for dict, list, string, number, etc.
    """
    merged = dict(d1)  # start with a copy of d1

    for key, value in d2.items():
        if key not in merged:
            # New key, just add
            merged[key] = value

    return merged


def create_relationship_for_malware(malware: dict) -> list:
    """
    Create a list of relationships objects from the malware and threat actor.

    :type malware: ``dict``
    :param malware: Malware of API.

    :return: List of EntityRelationship objects containing all the relationships.
    :rtype: ``List``
    """
    relationships = []
    source_malware = malware.get("name", "")
    integration_reliability = demisto.params().get("integrationReliability", DEFAULT_RELIABILITY)

    for threat_actor in malware.get("threatActors", []):
        threat_actor_name = "Threat Actor: [" + threat_actor.get("name", "") + "]"
        relationships.append(
            EntityRelationship(
                name=EntityRelationship.Relationships.ORIGINATED_FROM,
                entity_a=source_malware,
                entity_a_type="Dataminr Pulse Malware Indicator",
                entity_b=threat_actor_name,
                entity_b_type="Dataminr Pulse Threat Actor Indicator",
                source_reliability=integration_reliability,
                brand=VENDOR_NAME,
            )
        )

    return relationships


""" COMMAND FUNCTIONS """


def test_module(client: DataminrPulseReGenAIClient) -> str:
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises:
     exceptions if something goes wrong.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :rtype: ``str``
    :return: 'ok' if test passed, anything else will fail the test.
    """
    params = demisto.params()
    is_fetch = params.get("isFetch", False)
    if is_fetch:
        fetch_incidents(client, {}, params, is_test=True)
    else:
        client.get_alerts(page_size=1)
    # Return OK, indicating the connection to the platform is successful.
    return "ok"


def fetch_incidents(
    client: DataminrPulseReGenAIClient, last_run: dict[str, Any], params: dict[str, Any], is_test: bool = False
) -> tuple[dict, list]:
    """Fetch alerts as an incidents.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :type last_run: ``dict[str, Any]``
    :param last_run: last run object obtained from demisto.getLastRun().

    :type params: `dict[str, Any]``
    :param params: Arguments to be used for fetch incident.

    :type is_test: ``bool``
    :param is_test: If test_module called fetch_incident.

    :rtype: ``tuple[dict, list]``
    :return: Tuple of last run object and list of fetched incidents.
    """
    page_size: int = arg_to_number(
        params.get("max_fetch", DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE),  # type: ignore
        arg_name="Max Fetch",
    )
    watchlist_names = argToList(params.get("watchlist_names"), transform=lambda s: s.strip())
    query = params.get("query")
    alert_type: str = params.get("alert_type", DEFAULT_ALERT_TYPE)

    if query:
        query = query.strip()

    _from = last_run.get("from")
    last_watchlist_names = last_run.get("last_watchlist_names", [])
    last_query = last_run.get("last_query")
    found_alert_ids = last_run.get("found_alert_ids", [])

    watchlist_names_lower = []
    last_watchlist_names_lower = []
    if watchlist_names and isinstance(watchlist_names, list):
        watchlist_names_lower = [name.lower() for name in watchlist_names]
        watchlist_names_lower.sort()

    if last_watchlist_names and isinstance(last_watchlist_names, list):
        last_watchlist_names_lower = [name.lower() for name in last_watchlist_names]
        last_watchlist_names_lower.sort()

    watchlist_ids = get_watchlist_ids(client=client, watchlist_names=watchlist_names)
    if last_watchlist_names_lower != watchlist_names_lower or last_query != query:
        demisto.debug("Watchlist names or query changed in configuration, so fetching incident from start")
        _from = None

    if not is_test and page_size > 100:
        page_size = 100

    validate_params_for_alerts_get(
        watchlist_ids=watchlist_ids,
        watchlist_names=watchlist_names,
        page_size=page_size,
        use_configured_watchlist_names=False,
        is_fetch=True,
    )

    response = client.get_alerts(watchlist_ids=watchlist_ids, query=query, _from=_from, page_size=page_size)

    alert_response, _from, to = [], "", ""
    if response:
        alert_response = response.get("alerts", [])
        _from = response.get("previousPage", "")
        to = response.get("nextPage", "")

    alert_valid_response = remove_empty_elements_for_fetch(alert_response)

    from_url = urlparse(_from)
    from_params = parse_qs(from_url.query)
    from_value = from_params.get("to", [None])[0]  # type: ignore

    to_url = urlparse(to)
    to_params = parse_qs(to_url.query)
    to_value = to_params.get("from", [None])[0]  # type: ignore

    cursor_response = {"from": from_value, "to": to_value}
    cursor_valid_response = remove_empty_elements(cursor_response)

    if is_test:
        return {}, []

    next_run = last_run.copy()

    incidents = []

    duplicate_alert_ids_in_current_fetch = []
    new_alert_ids_in_current_fetch = []

    for alert in alert_valid_response:
        alert_type_name = alert.get("alertType", {}).get("name", "")
        if alert_type != "All" and (not alert_type_name or alert_type_name.lower() != alert_type.lower()):
            continue
        alert_id = alert.get("alertId")
        if alert_id in found_alert_ids:
            demisto.debug(f"Found existing alert. Alert ID: {alert_id}")
            duplicate_alert_ids_in_current_fetch.append(alert_id)
            continue

        media_html = create_media_html(alert)
        alert["media_html"] = media_html

        vulnerability_indicators = create_vulnerability_indicators(alert)
        alert["vulnerability_indicators"] = vulnerability_indicators

        malware_indicators = create_malware_indicators(alert)
        alert["malware_indicators"] = malware_indicators

        threat_actors_indicators = create_threat_actors_indicators(alert)
        alert["threat_actors_indicators"] = threat_actors_indicators

        flight_data = create_flight_data(alert)
        alert["flight_data"] = flight_data

        occurred_date = alert.get("alertTimestamp", "")
        incident_name = alert.get("headline", "")

        incidents.append(
            {
                "name": "" + incident_name if alert.get("intelAgents", []) else incident_name,
                "occurred": occurred_date,
                "rawJSON": json.dumps(alert),
                "severity": ALERT_TYPE_TO_INCIDENT_SEVERITY.get(alert_type_name, 0),
            }
        )
        found_alert_ids.append(alert_id)
        new_alert_ids_in_current_fetch.append(alert_id)

    next_run["found_alert_ids"] = found_alert_ids

    if alert_valid_response and cursor_valid_response:
        next_run["from"] = cursor_valid_response.get("to")
        next_run["last_watchlist_names"] = watchlist_names
        next_run["last_query"] = query

    demisto.debug(f"Total alerts fetch as an incident: {len(incidents)}")
    demisto.debug(f"New alert IDs in current fetch: {new_alert_ids_in_current_fetch}")
    demisto.debug(f"Duplicate alert IDs in current fetch: {duplicate_alert_ids_in_current_fetch}")
    demisto.debug(f"All Alert IDs store in last run: {next_run['found_alert_ids']}")
    demisto.debug(f"Next page cursor: {next_run['from']}")

    return next_run, incidents


def dataminrpulse_watchlists_get_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> CommandResults:
    """Retrieve the Watchlist stored on the Dataminr platform.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    # Retrieve the lists stored on the Dataminr platform.
    raw_lists_resp = client.get_watchlists()
    list_of_watchlists = transform_watchlists_data(raw_lists_resp)
    # Create a human-readable output for the war room entry.
    hr_output = prepare_hr_for_watchlists_get(list_of_watchlists)
    # Create and return a CommandResults object to return_results function.
    return CommandResults(
        outputs_prefix=OUTPUT_PREFIX_WATCHLISTS,
        outputs_key_field="id",
        outputs=list_of_watchlists,
        readable_output=hr_output,
        raw_response=raw_lists_resp,
    )


def dataminrpulse_alerts_get(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> List[CommandResults]:
    """Retrieve the list of the alerts that meet the specified filter criteria.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``List[CommandResults]``
    :return: Standard command results.
    """
    watchlist_names: List = argToList(args.get("watchlist_names", ""))
    watchlist_ids: List = argToList(args.get("watchlist_ids", ""))
    query: str = args.get("query", "")
    _from: str = args.get("from", "")
    to: str = args.get("to", "")
    num: int = arg_to_number(args.get("num", DEFAULT_NUMBER_OF_ALERTS_TO_RETRIEVE), arg_name="num")  # type: ignore
    use_configured_watchlist_names: bool = argToBoolean(args.get("use_configured_watchlist_names", "yes"))

    if use_configured_watchlist_names and not watchlist_ids:
        watchlist_ids = get_watchlist_ids(client, watchlist_names)

    validate_params_for_alerts_get(
        watchlist_ids=watchlist_ids,
        watchlist_names=watchlist_names,
        page_size=num,
        use_configured_watchlist_names=use_configured_watchlist_names,
        _from=_from,
        to=to,
    )

    response = client.get_alerts(watchlist_ids, query, _from, to, num)
    alert_response = response.get("alerts", [])  # type: ignore
    alert_valid_response = remove_empty_elements(alert_response)
    hr_output_for_alerts = prepare_hr_for_alerts(alert_valid_response)

    _from = response.get("previousPage", "")  # type: ignore
    to = response.get("nextPage", "")  # type: ignore

    from_url = urlparse(_from)
    from_params = parse_qs(from_url.query)
    from_value = from_params.get("to", [None])[0]  # type: ignore

    to_url = urlparse(to)
    to_params = parse_qs(to_url.query)
    to_value = to_params.get("from", [None])[0]  # type: ignore

    cursor_response = {"from": from_value, "to": to_value}
    cursor_valid_response = remove_empty_elements(cursor_response)

    hr_output_for_cursor = prepare_hr_for_cursor(cursor_valid_response)

    alert_results = CommandResults(
        outputs_prefix=OUTPUT_PREFIX_ALERTS,
        outputs_key_field="alertId",
        outputs=alert_valid_response,
        readable_output=hr_output_for_alerts,
        raw_response=response,
    )

    cursor_results = CommandResults(
        outputs_prefix=OUTPUT_PREFIX_CURSOR,
        outputs_key_field=["from", "to"],
        outputs=cursor_valid_response,
        readable_output=hr_output_for_cursor,
        raw_response=cursor_response,
    )

    return [alert_results, cursor_results]


def dataminrpulse_vulnerability_enrich_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> list[CommandResults]:
    """
    Enrich the "Dataminr Pulse Vulnerability Indicator" custom indicator with relevant data.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    vulnerability_json_data: Any = args.get("vulnerability_json_data")
    vulnerability_list: list = []

    if not vulnerability_json_data:
        raise ValueError(ERRORS["REQUIRED_ARG"].format("vulnerability_json_data"))

    try:
        vulnerability_list = json.loads(vulnerability_json_data)
    except json.JSONDecodeError:
        raise ValueError(ERRORS["JSON_DECODE"].format("vulnerability_json_data"))

    results = []
    if not vulnerability_list:
        return CommandResults(readable_output="No vulnerabilities found.")  # type: ignore

    for vulnerability in vulnerability_list:
        indicator_value = vulnerability.get("id", "")

        dbot_score = Common.DBotScore(
            indicator=indicator_value,
            indicator_type=DBotScoreType.CUSTOM,
            integration_name=VENDOR_NAME,
            score=calculate_dbot_score(float(vulnerability.get("cvss", Common.DBotScore.NONE))),
            reliability=demisto.params().get("integrationReliability", DEFAULT_RELIABILITY),
        )

        custom_indicator = Common.CustomIndicator(
            value=indicator_value,
            indicator_type="Dataminr Pulse Vulnerability Indicator",
            data=remove_empty_elements(vulnerability),
            context_prefix="DataminrPulseVulnerabilityIndicator",
            dbot_score=dbot_score,
        )

        hr = tableToMarkdown("Vulnerability", remove_empty_elements_for_hr(vulnerability), is_auto_json_transform=True)

        results.append(
            CommandResults(
                outputs=remove_empty_elements(vulnerability),
                outputs_prefix=CUSTOM_OUTPUT_PREFIX.format("Vulnerability"),
                outputs_key_field="id",
                indicator=custom_indicator,
                raw_response=vulnerability,
                readable_output=hr,
            )
        )

    return results


def dataminrpulse_malware_enrich_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> list[CommandResults]:
    """
    Enrich the "Dataminr Pulse Malware Indicator" custom indicator with relevant data.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    malware_json_data: Any = args.get("malware_json_data")
    malware_list: list = []

    if not malware_json_data:
        raise ValueError(ERRORS["REQUIRED_ARG"].format("malware_json_data"))

    try:
        malware_list = json.loads(malware_json_data)
    except json.JSONDecodeError:
        raise ValueError(ERRORS["JSON_DECODE"].format("malware_json_data"))

    results = []
    if not malware_list:
        return CommandResults(readable_output="No malware found.")  # type: ignore

    for malware in malware_list:
        indicator_value = malware.get("name", "")

        dbot_score = Common.DBotScore(
            indicator=indicator_value,
            indicator_type=DBotScoreType.CUSTOM,
            integration_name=VENDOR_NAME,
            score=Common.DBotScore.BAD,
            reliability=demisto.params().get("integrationReliability", DEFAULT_RELIABILITY),
        )

        relationships = []
        if demisto.params().get("create_relationships", True):
            relationships = create_relationship_for_malware(malware)

        custom_indicator = Common.CustomIndicator(
            value=indicator_value,
            indicator_type="Dataminr Pulse Malware Indicator",
            data=remove_empty_elements(malware),
            context_prefix="DataminrPulseMalwareIndicator",
            dbot_score=dbot_score,
            relationships=relationships,
        )

        hr = tableToMarkdown("Malware", remove_empty_elements_for_hr(malware), is_auto_json_transform=True)

        results.append(
            CommandResults(
                outputs=remove_empty_elements(malware),
                outputs_prefix=CUSTOM_OUTPUT_PREFIX.format("Malware"),
                outputs_key_field="name",
                indicator=custom_indicator,
                raw_response=malware,
                readable_output=hr,
            )
        )

    return results


def dataminrpulse_threat_actor_enrich_command(client: DataminrPulseReGenAIClient, args: Dict[str, Any]) -> list[CommandResults]:
    """
    Enrich the "Dataminr Pulse Threat Actor Indicator" custom indicator with relevant data.

    :type client: ``DataminrPulseReGenAIClient``
    :param client: DataminrPulseReGenAIClient to be used.

    :type args: ``Dict[str, Any]``
    :param args: Arguments provided by user.

    :rtype: ``CommandResults``
    :return: Standard command result.
    """
    threat_actor_json_data: Any = args.get("threat_actor_json_data")
    threat_actor_list: list = []

    if not threat_actor_json_data:
        raise ValueError(ERRORS["REQUIRED_ARG"].format("threat_actor_json_data"))

    try:
        threat_actor_list = json.loads(threat_actor_json_data)
    except json.JSONDecodeError:
        raise ValueError(ERRORS["JSON_DECODE"].format("threat_actor_json_data"))

    results = []
    if not threat_actor_list:
        return CommandResults(readable_output="No threat actors found.")  # type: ignore

    for threat_actor in threat_actor_list:
        indicator_value = threat_actor.get("name", "")

        dbot_score = Common.DBotScore(
            indicator=indicator_value,
            indicator_type=DBotScoreType.CUSTOM,
            integration_name=VENDOR_NAME,
            score=Common.DBotScore.BAD,
            reliability=demisto.params().get("integrationReliability", DEFAULT_RELIABILITY),
        )

        custom_indicator = Common.CustomIndicator(
            value=indicator_value,
            indicator_type="Dataminr Pulse Threat Actor Indicator",
            data=remove_empty_elements(threat_actor),
            context_prefix="DataminrPulseThreatActorIndicator",
            dbot_score=dbot_score,
        )

        hr = tableToMarkdown("Threat Actor", remove_empty_elements_for_hr(threat_actor), is_auto_json_transform=True)

        results.append(
            CommandResults(
                outputs=remove_empty_elements(threat_actor),
                outputs_prefix=CUSTOM_OUTPUT_PREFIX.format("ThreatActor"),
                outputs_key_field="name",
                indicator=custom_indicator,
                raw_response=threat_actor,
                readable_output=hr,
            )
        )

    return results


def main():
    """main function, parses params and runs command functions"""

    # Retrieve the configuration parameters.
    params = trim_spaces_from_args(demisto.params())
    remove_nulls_from_dictionary(params)

    # Credentials for connecting with the Dataminr Pulse API.
    client_id = params.get("credentials", {}).get("identifier")
    client_secret = params.get("credentials", {}).get("password")

    # Default configuration parameters for handling proxy and SSL Certificate validation.
    verify_certificate = not argToBoolean(params.get("insecure", False))
    proxy = argToBoolean(params.get("proxy", False))

    # Parameters for fetch incident mechanism
    watchlist_names = params.get("watchlist_names")

    # Retrieve the name of the command being called.
    command = demisto.command()
    demisto.debug(f"The command being called is {command}.")

    demisto_commands: Dict[str, Callable] = {
        "dataminrpulse-watchlists-get": dataminrpulse_watchlists_get_command,
        "dataminrpulse-alerts-get": dataminrpulse_alerts_get,
        "dataminrpulse-vulnerability-enrich": dataminrpulse_vulnerability_enrich_command,
        "dataminrpulse-malware-enrich": dataminrpulse_malware_enrich_command,
        "dataminrpulse-threat-actor-enrich": dataminrpulse_threat_actor_enrich_command,
    }

    try:
        client = DataminrPulseReGenAIClient(
            client_id=client_id, client_secret=client_secret, proxy=proxy, verify=verify_certificate
        )

        # Execute the respective command function based on the command name got from the Demisto.
        if command == "test-module":
            return_results(test_module(client))
        elif command == "fetch-incidents":
            last_run = demisto.getLastRun()
            next_run, incidents = fetch_incidents(client, last_run, params)
            demisto.info(f"Fetched {len(incidents)} new incidents")
            demisto.incidents(incidents)
            demisto.setLastRun(next_run)
        elif command in demisto_commands:
            args = demisto.args()
            if command == "dataminrpulse-alerts-get":
                args.update({"watchlist_names": watchlist_names})
            remove_nulls_from_dictionary(trim_spaces_from_args(args))
            return_results(demisto_commands[command](client, args))
        else:
            raise NotImplementedError(f"The command {command} is not implemented.")

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


if __name__ in ("__main__", "__builtin__", "builtins"):  # pragma: no cover
    main()