diff --git a/src/handlers/sigma/sigma_create_handler.js b/src/handlers/sigma/sigma_create_handler.js index 1965f1c..a131b36 100644 --- a/src/handlers/sigma/sigma_create_handler.js +++ b/src/handlers/sigma/sigma_create_handler.js @@ -2,7 +2,6 @@ * sigma_create_handler.js * * Handles Sigma rule conversion requests from Slack commands - * Action handlers moved to sigma_action_core.js */ const logger = require('../../utils/logger'); const { handleError } = require('../../utils/error_handler'); diff --git a/src/handlers/sigma/sigma_details_handler.js b/src/handlers/sigma/sigma_details_handler.js index 079db7d..d6122c7 100644 --- a/src/handlers/sigma/sigma_details_handler.js +++ b/src/handlers/sigma/sigma_details_handler.js @@ -22,6 +22,9 @@ const handleCommand = async (command, respond) => { try { logger.debug(`${FILE_NAME}: Processing sigma-details command: ${command.text}`); + // Determine if request is from CLI + const isCliRequest = command.channel_id === 'cli' || command.channel_name === 'cli'; + if (!command || !command.text) { logger.warn(`${FILE_NAME}: Empty command received for sigma-details`); await respond({ @@ -61,25 +64,28 @@ const handleCommand = async (command, respond) => { return; } - // For Slack responses, generate Block Kit blocks - let blocks; - try { - // This is for Slack - get the Block Kit UI components - blocks = getSigmaRuleDetailsBlocks(sigmaRuleDetailsResult.explanation); - } catch (blockError) { - await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { - responseType: 'ephemeral', - customMessage: 'Error generating rule details view' + // Create response based on interface type + if (isCliRequest) { + // For CLI, just return the raw data + await respond({ + responseData: sigmaRuleDetailsResult.explanation, + response_type: 'cli' }); - return; + } else { + // For Slack, generate and return Block Kit blocks + try { + const blocks = getSigmaRuleDetailsBlocks(sigmaRuleDetailsResult.explanation); + await respond({ + blocks: blocks, + response_type: 'in_channel' + }); + } catch (blockError) { + await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { + responseType: 'ephemeral', + customMessage: 'Error generating rule details view' + }); + } } - - // Return the response with both blocks for Slack and responseData for CLI - await respond({ - blocks: blocks, // For Slack interface - responseData: sigmaRuleDetailsResult.explanation, // For CLI interface - response_type: 'in_channel' - }); } catch (error) { await handleError(error, `${FILE_NAME}: Details command handler`, respond, { responseType: 'ephemeral' diff --git a/src/handlers/sigma/sigma_search_handler.js b/src/handlers/sigma/sigma_search_handler.js index 7613239..fe4d645 100644 --- a/src/handlers/sigma/sigma_search_handler.js +++ b/src/handlers/sigma/sigma_search_handler.js @@ -24,18 +24,18 @@ const MAX_RESULTS_THRESHOLD = 99; const handleCommand = async (command, respond) => { try { logger.debug(`${FILE_NAME}: Processing sigma-search command: ${JSON.stringify(command.text)}`); - + if (!command || !command.text) { logger.warn(`${FILE_NAME}: Empty command received for sigma-search`); await respond('Invalid command. Usage: /sigma-search [keyword]'); return; } - + // Extract search keyword and check for pagination parameters let keyword = command.text.trim(); let page = 1; let pageSize = MAX_RESULTS_PER_PAGE; - + // Check for pagination format: keyword page=X const pagingMatch = keyword.match(/(.+)\s+page=(\d+)$/i); if (pagingMatch) { @@ -43,7 +43,7 @@ const handleCommand = async (command, respond) => { page = parseInt(pagingMatch[2], 10) || 1; logger.debug(`${FILE_NAME}: Detected pagination request: "${keyword}" page ${page}`); } - + // Check for page size format: keyword limit=X const limitMatch = keyword.match(/(.+)\s+limit=(\d+)$/i); if (limitMatch) { @@ -53,27 +53,27 @@ const handleCommand = async (command, respond) => { pageSize = Math.min(Math.max(pageSize, 1), 100); logger.debug(`${FILE_NAME}: Detected page size request: "${keyword}" limit ${pageSize}`); } - + if (!keyword) { logger.warn(`${FILE_NAME}: Missing keyword in sigma-search command`); await respond('Invalid command: missing keyword. Usage: /sigma-search [keyword]'); return; } - + logger.info(`${FILE_NAME}: Searching for rules with keyword: ${keyword} (page ${page}, size ${pageSize})`); logger.debug(`${FILE_NAME}: Search keyword length: ${keyword.length}`); - + await respond({ text: 'Searching for rules... This may take a moment.', response_type: 'ephemeral' }); - + // Search for rules using the service function with pagination const searchResult = await searchAndConvertRules(keyword, page, pageSize); logger.debug(`${FILE_NAME}: Search result status: ${searchResult.success}`); logger.debug(`${FILE_NAME}: Found ${searchResult.results?.length || 0} results out of ${searchResult.pagination?.totalResults || 0} total matches`); logger.debug(`${FILE_NAME}: About to generate blocks for search results`); - + if (!searchResult.success) { logger.error(`${FILE_NAME}: Search failed: ${searchResult.message}`); await respond({ @@ -82,17 +82,17 @@ const handleCommand = async (command, respond) => { }); return; } - + // Get total count for validation const totalCount = searchResult.pagination?.totalResults || 0; - + // Check if search returned too many results if (totalCount > MAX_RESULTS_THRESHOLD) { logger.warn(`${FILE_NAME}: Search for "${keyword}" returned too many results (${totalCount}), displaying first page with warning`); // Continue processing but add a notification searchResult.tooManyResults = true; } - + if (!searchResult.results || searchResult.results.length === 0) { if (totalCount > 0) { logger.warn(`${FILE_NAME}: No rules found on page ${page} for "${keyword}", but ${totalCount} total matches exist`); @@ -109,48 +109,57 @@ const handleCommand = async (command, respond) => { } return; } - - // Generate blocks with pagination support - let blocks; - try { - logger.debug(`${FILE_NAME}: Calling getSearchResultBlocks with ${searchResult.results.length} results`); - // If we have too many results, add a warning block at the beginning - if (searchResult.tooManyResults) { - blocks = getSearchResultBlocks(keyword, searchResult.results, searchResult.pagination); - // Insert warning at the beginning of blocks (after the header) - blocks.splice(1, 0, { - "type": "section", - "text": { - "type": "mrkdwn", - "text": `:warning: Your search for "${keyword}" returned ${totalCount} results, which is a lot. Displaying the first page. Consider using a more specific keyword for narrower results.` - } - }); - } else { - blocks = getSearchResultBlocks(keyword, searchResult.results, searchResult.pagination); - } - logger.debug(`${FILE_NAME}: Successfully generated ${blocks?.length || 0} blocks`); - } catch (blockError) { - // Use error handler for block generation errors - await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { - responseType: 'in_channel', - customMessage: `Found ${searchResult.results.length} of ${totalCount} rules matching "${keyword}" (page ${page} of ${searchResult.pagination?.totalPages || 1}). Use /sigma-details [id] to view details.` + + const isCliRequest = command.channel_id === 'cli' || command.channel_name === 'cli'; + + if (isCliRequest) { + // For CLI, just return the raw data + await respond({ + responseData: searchResult.results, + response_type: 'cli' }); - return; + } else { + // For Slack, generate and return Block Kit blocks + let blocks; + try { + logger.debug(`${FILE_NAME}: Calling getSearchResultBlocks with ${searchResult.results.length} results`); + // If we have too many results, add a warning block at the beginning + if (searchResult.tooManyResults) { + blocks = getSearchResultBlocks(keyword, searchResult.results, searchResult.pagination); + // Insert warning at the beginning of blocks (after the header) + blocks.splice(1, 0, { + "type": "section", + "text": { + "type": "mrkdwn", + "text": `:warning: Your search for "${keyword}" returned ${totalCount} results, which is a lot. Displaying the first page. Consider using a more specific keyword for narrower results.` + } + }); + } else { + blocks = getSearchResultBlocks(keyword, searchResult.results, searchResult.pagination); + } + logger.debug(`${FILE_NAME}: Successfully generated ${blocks?.length || 0} blocks`); + + // Determine if this should be visible to everyone or just the user + const isEphemeral = totalCount > 20; + + // Add debug log before sending response + logger.debug(`${FILE_NAME}: About to send response with ${blocks?.length || 0} blocks`); + + // Respond with the search results + // Respond with the search results + await respond({ + blocks: blocks, + response_type: isEphemeral ? 'ephemeral' : 'in_channel' + }); + } catch (blockError) { + // Use error handler for block generation errors + await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { + responseType: 'in_channel', + customMessage: `Found ${searchResult.results.length} of ${totalCount} rules matching "${keyword}" (page ${page} of ${searchResult.pagination?.totalPages || 1}). Use /sigma-details [id] to view details.` + }); + } } - - // Add debug log before sending response - logger.debug(`${FILE_NAME}: About to send response with ${blocks?.length || 0} blocks`); - - // Determine if this should be visible to everyone or just the user - const isEphemeral = totalCount > 20; - - // Respond with the search results - await respond({ - blocks: blocks, - responseData: searchResult.results, - response_type: isEphemeral ? 'ephemeral' : 'in_channel' - }); - + // Add debug log after sending response logger.debug(`${FILE_NAME}: Response sent successfully`); } catch (error) { @@ -171,18 +180,18 @@ const handleCommand = async (command, respond) => { const handleComplexSearch = async (command, respond) => { try { logger.debug(`${FILE_NAME}: Processing complex search command: ${JSON.stringify(command.text)}`); - + if (!command || !command.text) { logger.warn(`${FILE_NAME}: Empty command received for complex search`); await respond('Invalid command. Usage: /sigma-search where [conditions]'); return; } - + // Extract query string let queryString = command.text.trim(); let page = 1; let pageSize = MAX_RESULTS_PER_PAGE; - + // Check for pagination format: query page=X const pagingMatch = queryString.match(/(.+)\s+page=(\d+)$/i); if (pagingMatch) { @@ -190,7 +199,7 @@ const handleComplexSearch = async (command, respond) => { page = parseInt(pagingMatch[2], 10) || 1; logger.debug(`${FILE_NAME}: Detected pagination request in complex search: page ${page}`); } - + // Check for page size format: query limit=X const limitMatch = queryString.match(/(.+)\s+limit=(\d+)$/i); if (limitMatch) { @@ -200,17 +209,17 @@ const handleComplexSearch = async (command, respond) => { pageSize = Math.min(Math.max(pageSize, 1), 100); logger.debug(`${FILE_NAME}: Detected page size request in complex search: limit ${pageSize}`); } - + logger.info(`${FILE_NAME}: Performing complex search with query: ${queryString}`); - + await respond({ text: 'Processing complex search query... This may take a moment.', response_type: 'ephemeral' }); - + // Perform the complex search const searchResult = await searchSigmaRulesComplex(queryString, page, pageSize); - + if (!searchResult.success) { logger.error(`${FILE_NAME}: Complex search failed: ${searchResult.message}`); await respond({ @@ -219,7 +228,7 @@ const handleComplexSearch = async (command, respond) => { }); return; } - + // Check if we have results if (!searchResult.results || searchResult.results.length === 0) { logger.warn(`${FILE_NAME}: No rules found matching complex query criteria`); @@ -229,17 +238,17 @@ const handleComplexSearch = async (command, respond) => { }); return; } - + // Generate blocks with pagination support let blocks; try { // Use the standard search result blocks but with a modified header blocks = getSearchResultBlocks( - `Complex Query: ${queryString}`, - searchResult.results, + `Complex Query: ${queryString}`, + searchResult.results, searchResult.pagination ); - + // Replace the header to indicate it's a complex search // TODO: should be moved to dedicated block file if (blocks && blocks.length > 0) { @@ -251,7 +260,7 @@ const handleComplexSearch = async (command, respond) => { emoji: true } }; - + // Add a description of the search criteria blocks.splice(1, 0, { type: "section", @@ -268,14 +277,14 @@ const handleComplexSearch = async (command, respond) => { }); return; } - + // Respond with the search results await respond({ blocks: blocks, responseData: searchResult.results, response_type: 'ephemeral' // Complex searches are usually more specific to the user }); - + logger.info(`${FILE_NAME}: Complex search response sent successfully with ${searchResult.results.length} results`); } catch (error) { await handleError(error, `${FILE_NAME}: Complex search handler`, respond, { diff --git a/src/handlers/sigma/sigma_stats_handler.js b/src/handlers/sigma/sigma_stats_handler.js index d92e126..eb8b785 100644 --- a/src/handlers/sigma/sigma_stats_handler.js +++ b/src/handlers/sigma/sigma_stats_handler.js @@ -22,6 +22,9 @@ const handleCommand = async (command, respond) => { try { logger.info(`${FILE_NAME}: Processing sigma-stats command`); + // Determine if request is from CLI by checking channel properties + const isCliRequest = command.channel_id === 'cli' || command.channel_name === 'cli'; + await respond({ text: 'Gathering Sigma rule statistics... This may take a moment.', response_type: 'ephemeral' @@ -39,24 +42,28 @@ const handleCommand = async (command, respond) => { return; } - // For Slack responses, generate Block Kit blocks - let blocks; - try { - blocks = getStatsBlocks(statsResult.stats); - } catch (blockError) { - await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { - responseType: 'ephemeral', - customMessage: 'Error generating statistics view' + // For CLI, only include responseData + if (isCliRequest) { + await respond({ + responseData: statsResult.stats, + response_type: 'cli' }); - return; + } + // For Slack, only generate Block Kit blocks + else { + try { + const blocks = getStatsBlocks(statsResult.stats); + await respond({ + blocks: blocks, + response_type: 'in_channel' + }); + } catch (blockError) { + await handleError(blockError, `${FILE_NAME}: Block generation`, respond, { + responseType: 'ephemeral', + customMessage: 'Error generating statistics view' + }); + } } - - // Return the response with both blocks for Slack and responseData for CLI - await respond({ - blocks: blocks, - responseData: statsResult.stats, // Include raw data for CLI - response_type: 'in_channel' - }); } catch (error) { await handleError(error, `${FILE_NAME}: Stats command handler`, respond, { responseType: 'ephemeral'