// js-src/counter/index.js - Counter Command (Refactored for Readability) // --- Constants --- // Interaction Types (from Discord API) const InteractionType = { APPLICATION_COMMAND: 2, MESSAGE_COMPONENT: 3, MODAL_SUBMIT: 5, }; // Component Types (from Discord API) const ComponentType = { ACTION_ROW: 1, BUTTON: 2, // Add SELECT_MENU, TEXT_INPUT etc. here if needed }; // Button Styles (from Discord API) const ButtonStyle = { PRIMARY: 1, // Blue SECONDARY: 2, // Gray SUCCESS: 3, // Green DANGER: 4, // Red LINK: 5, }; const PLUGIN_NAME = "counter"; // Used in Custom IDs // --- Command Definition --- const counterCommandDefinition = { Name: PLUGIN_NAME, Description: "Starts an interactive counter (readable version).", Options: [], }; function get_command_definition() { try { // Output the command definition JSON for the Go host Host.outputString(JSON.stringify(counterCommandDefinition)); return 0; // Success } catch (e) { console.error(`[${PLUGIN_NAME}] Error stringifying command definition:`, e); Host.outputString(`{"error": "Failed to generate command definition"}`); return 1; // Indicate failure } } // --- Helper Functions --- /** * Parses the counter's custom ID format "counter:action:count". * @param {string} customId - The custom ID string. * @returns {object|null} An object { action: string, count: number } or null if invalid. */ function parseCounterCustomId(customId) { if (!customId || typeof customId !== 'string') { console.error(`[${PLUGIN_NAME}] Invalid customId type received:`, customId); return null; } const parts = customId.split(':'); // Expecting 3 parts: pluginName, action, countString if (parts.length === 3 && parts[0] === PLUGIN_NAME) { const action = parts[1]; const count = parseInt(parts[2], 10); // Base 10 if (!isNaN(count) && (action === 'increment' || action === 'decrement')) { // Successfully parsed return { action, count }; } else { console.error(`[${PLUGIN_NAME}] Failed to parse valid action/count from CustomID parts:`, parts); } } else { console.error(`[${PLUGIN_NAME}] CustomID did not match expected format 'pluginName:action:count':`, customId); } return null; // Indicate failure } /** * Creates the Action Row containing the counter buttons. * @param {number} currentCount - The count to embed in the buttons' custom IDs. * @returns {Array} An array containing a single Action Row component object. */ function createCounterActionRow(currentCount) { return [ // Array always starts with Action Rows { type: ComponentType.ACTION_ROW, components: [ // Components within the Action Row { type: ComponentType.BUTTON, style: ButtonStyle.PRIMARY, label: "Increment (+)", custom_id: `${PLUGIN_NAME}:increment:${currentCount}` }, { type: ComponentType.BUTTON, style: ButtonStyle.DANGER, label: "Decrement (-)", custom_id: `${PLUGIN_NAME}:decrement:${currentCount}` } ] } ]; } /** * Creates the JSON response object to send back to the Go host. * @param {string} content - The message content. * @param {Array|null} components - The array of components (e.g., action rows), or null. * @param {boolean} ephemeral - Whether the response should be ephemeral. * @returns {object} The response object ready for stringification. */ function createPluginResponse(content, components = null, ephemeral = false) { const response = { content, ephemeral }; if (components) { response.components = components; } return response; } // --- Main Interaction Handler --- function handle_interaction() { console.log(`[${PLUGIN_NAME}] handle_interaction called.`); try { // --- 1. Get Input Data --- const inputJson = Host.inputString(); console.log(`[${PLUGIN_NAME}] Raw Input (first 500):`, inputJson.substring(0, 500)); let interactionData; try { interactionData = JSON.parse(inputJson); } catch (parseErr) { console.error(`[${PLUGIN_NAME}] Failed to parse input JSON:`, parseErr); const errResp = createPluginResponse("Error: Couldn't understand interaction data.", null, true); Host.outputString(JSON.stringify(errResp)); return 1; // Indicate failure } console.log(`[${PLUGIN_NAME}] Parsed Interaction Type:`, interactionData.type); // --- 2. Determine Current State & Action --- let currentCount = 0; let action = "start"; // Default for initial command if (interactionData.type === InteractionType.MESSAGE_COMPONENT) { console.log(`[${PLUGIN_NAME}] Processing Message Component interaction.`); const customId = interactionData.data.custom_id; const parsedId = parseCounterCustomId(customId); if (parsedId) { action = parsedId.action; currentCount = parsedId.count; console.log(`[${PLUGIN_NAME}] Parsed action: ${action}, count: ${currentCount}`); } else { // Error already logged in parseCounterCustomId const errResp = createPluginResponse("Error: Invalid button state received.", null, true); Host.outputString(JSON.stringify(errResp)); return 0; // Return normally but show error } } else if (interactionData.type === InteractionType.APPLICATION_COMMAND) { console.log(`[${PLUGIN_NAME}] Processing Application Command interaction (start).`); // Use default action='start', currentCount=0 } else { console.error(`[${PLUGIN_NAME}] Received unexpected interaction type:`, interactionData.type); const errResp = createPluginResponse("Error: Unexpected interaction type received.", null, true); Host.outputString(JSON.stringify(errResp)); return 0; // Return normally but show error } // --- 3. Calculate New State --- let newCount = currentCount; if (action === 'increment') { newCount++; } else if (action === 'decrement') { newCount--; } // 'start' action uses the initial newCount = currentCount = 0 console.log(`[${PLUGIN_NAME}] New count calculated: ${newCount}`); // --- 4. Construct Response --- const responseContent = `Count: ${newCount}`; const responseComponents = createCounterActionRow(newCount); const response = createPluginResponse(responseContent, responseComponents, false); console.log(`[${PLUGIN_NAME}] Sending response JSON:`, JSON.stringify(response)); Host.outputString(JSON.stringify(response)); return 0; // Success } catch (e) { // --- 5. Handle Unexpected Errors --- console.error(`[${PLUGIN_NAME}] Uncaught error in handle_interaction:`, e); // Send generic error back to user if possible const errResp = createPluginResponse(`Internal Error: ${e.message || 'Unknown error in counter plugin'}`, null, true); try { Host.outputString(JSON.stringify(errResp)); } catch (_) {} // Best effort error reporting return 1; // Indicate failure via exit code } } // --- Exports --- module.exports = { get_command_definition, handle_interaction, };