您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Enhanced Automation Anywhere developer experience. Working at CR Version 37.0.0
// ==UserScript== // @name Better AutomationAnywhere // @namespace http://tampermonkey.net/ // @version 0.5.0 // @description Enhanced Automation Anywhere developer experience. Working at CR Version 37.0.0 // @author jamir-boop // @match *://*.automationanywhere.digital/* // @icon https://cmpc-1dev.my.automationanywhere.digital/favicon.ico // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @license MIT // ==/UserScript== (function () { "use strict"; // ========================= // Section: State & Constants // ========================= /** @type {number} Tracks the active (highlighted) prediction in the command palette */ let activePredictionIndex = -1; /** @type {Object[]} Stores current predictions' actions for keyboard navigation */ let currentPredictionActions = []; let initialized = false; let updateActiveButtonIntervalId = null; // ========================= // Section: Utility Functions // ========================= /** * Waits for a DOM element to appear, then resolves. * @param {string} selector - CSS selector for the element. * @param {number} timeout - Max wait time in ms. * @returns {Promise<Element|null>} */ function waitForElement(selector, timeout = 5000) { return new Promise((resolve) => { const el = document.querySelector(selector); if (el) return resolve(el); const observer = new MutationObserver(() => { const found = document.querySelector(selector); if (found) { observer.disconnect(); resolve(found); } }); observer.observe(document.body, { childList: true, subtree: true }); setTimeout(() => { observer.disconnect(); resolve(null); }, timeout); }); } /** * Sleep for a given number of milliseconds. * @param {number} ms * @returns {Promise<void>} */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Safely query a DOM element and log a warning if not found. * @param {string} selector * @param {string} [context] * @returns {Element|null} */ function safeQuery(selector, context = "") { const el = document.querySelector(selector); if (!el) { console.warn(`Element not found: ${selector}${context ? " (" + context + ")" : ""}`); } return el; } /** * Adds a click event listener to an element if it exists. * @param {Element|null} el * @param {Function} handler */ function safeAddClick(el, handler) { if (el) el.addEventListener("click", handler); } // ========================= // Section: Command Definitions // ========================= /** @type {Object.<string, {action: Function, aliases: string[], description: string}>} */ const commandsWithAliases = { addVariable: { action: addVariable, aliases: ["adv", "addvar", "add variable"], description: "Shows dialog to create a new variable", }, showVariables: { action: showVariables, aliases: ["v", "showvars", "list variables", "variables"], description: "Shows variables in sidebar", }, deleteUnusedVariables: { action: deleteUnusedVariables, aliases: ["duv", "delete unused", "remove unused variables"], description: "Shows dialog to select and delete unused variables", }, redirectToPrivateRepository: { action: redirectToPrivateRepository, aliases: ["p", "private", "private bots"], description: "Redirects to the private bots folder", }, redirectToPublicRepository: { action: redirectToPublicRepository, aliases: ["pub", "public", "public bots"], description: "Redirects to the public bots folder", }, redirectToActivityHistorical: { action: redirectToActivityHistorical, aliases: ["historical", "history", "activity historical"], description: "Redirects to the activities historical tab", }, redirectToInProgress: { action: redirectToInProgress, aliases: ["inprogress", "progress", "in progress"], description: "Redirects to the in-progress activities tab", }, redirectToAuditLog: { action: redirectToAuditLog, aliases: ["audit", "audit log"], description: "Redirects to the activities historical tab", }, redirectToAdminUsers: { action: redirectToAdminUsers, aliases: ["users", "admin users", "manage users"], description: "Redirects to the admin users page", }, redirectToAdminRoles: { action: redirectToAdminRoles, aliases: ["roles", "admin roles", "manage roles"], description: "Redirects to the admin roles page", }, redirectToAdminDevices: { action: redirectToAdminDevices, aliases: ["devices", "admin devices", "manage devices"], description: "Redirects to the admin devices page", }, redirectToHome: { action: redirectToHome, aliases: ["home", "dashboard", "overview"], description: "Redirects to the dashboard home overview", }, showHelp: { action: showHelp, aliases: ["help", "man", "show help"], description: "Displays help information for available commands", }, universalCopy: { action: universalCopyCommandPalette, aliases: ["universal copy", "copy universal", "rocket copy"], description: "Copy actions between control rooms.", }, universalPaste: { action: universalPasteCommandPalette, aliases: ["universal paste", "paste universal", "rocket paste"], description: "Paste actions between control rooms.", }, exportActionToClipboard: { action: exportActionToClipboard, aliases: ["export action", "copy action json", "export copied action", "share action"], description: "Export the currently copied action as JSON to your clipboard.", }, importActionFromJson: { action: importActionFromJson, aliases: ["import action", "paste action json", "import shared action", "load action json"], description: "Import an action from JSON and paste it as if copied locally.", }, }; // ========================= // Section: Command Palette // ========================= /** * Returns the command palette element. * @returns {HTMLElement|null} */ function getCommandPalette() { return document.getElementById("commandPalette"); } /** * Returns the command input element. * @returns {HTMLInputElement|null} */ function getCommandInput() { return document.getElementById("commandInput"); } /** * Returns the command predictions container. * @returns {HTMLElement|null} */ function getCommandPredictions() { return document.getElementById("commandPredictions"); } /** * Toggles the command palette visibility. */ function togglePaletteVisibility() { const commandPalette = getCommandPalette(); if (!commandPalette) return; const input = getCommandInput(); if (commandPalette.classList.contains("command_palette--visible")) { commandPalette.classList.remove("command_palette--visible"); commandPalette.classList.add("command_palette--hidden"); if (input) { input.value = ""; input.blur(); } clearPredictions(); activePredictionIndex = -1; } else { commandPalette.classList.remove("command_palette--hidden"); commandPalette.classList.add("command_palette--visible"); if (input) { input.focus(); } } } /** * Clears command predictions. */ function clearPredictions() { const predictions = getCommandPredictions(); if (predictions) predictions.innerHTML = ""; } /** * Updates command predictions based on input. * @param {string} input */ function updatePredictions(input) { clearPredictions(); if (!input) { activePredictionIndex = -1; return; } // Check for ":<number>" syntax to scroll to a line const jumpToLineMatch = input.match(/^:(\d+)$/); if (jumpToLineMatch) { const lineNumber = parseInt(jumpToLineMatch[1], 10); const predictionItem = document.createElement("div"); predictionItem.classList.add("command_prediction-item"); predictionItem.innerHTML = `<strong>Go to line ${lineNumber}</strong>`; safeAddClick(predictionItem, () => { scrollToLineNumber(lineNumber); clearPredictions(); togglePaletteVisibility(); }); getCommandPredictions().appendChild(predictionItem); return; } Object.entries(commandsWithAliases).forEach( ([, { action, aliases, description }]) => { const match = aliases.find((alias) => alias.startsWith(input.toLowerCase()) ); if (match) { const predictionItem = document.createElement("div"); predictionItem.classList.add("command_prediction-item"); predictionItem.innerHTML = `<strong>${match}</strong> - ${description}`; safeAddClick(predictionItem, () => { const inputEl = getCommandInput(); if (inputEl) inputEl.value = match; executeCommand(action); clearPredictions(); }); getCommandPredictions().appendChild(predictionItem); } } ); // Always select the first prediction if any const predictionsContainer = getCommandPredictions(); const items = predictionsContainer ? predictionsContainer.getElementsByClassName("command_prediction-item") : []; if (items.length > 0) { activePredictionIndex = 0; updateActivePrediction(items); } else { activePredictionIndex = -1; } } /** * Sets up event listeners for the command input. */ function setupCommandInputEventListeners() { const commandInput = getCommandInput(); if (!commandInput) return; commandInput.addEventListener("input", function () { updatePredictions(this.value); }); commandInput.addEventListener("keydown", navigatePredictions); } /** * Executes a command action. * @param {Function} action */ function executeCommand(action) { if (action) { action(); } else { showHelp(); } togglePaletteVisibility(); } /** * Handles keyboard navigation in the command palette. * @param {KeyboardEvent} e */ function navigatePredictions(e) { const commandPredictions = getCommandPredictions(); if (!commandPredictions) return; const items = commandPredictions.getElementsByClassName("command_prediction-item"); if (!items.length) { if (e.key === "Escape") { togglePaletteVisibility(); e.preventDefault(); } return; } if (items.length === 1 && e.key === "Enter") { items[0].click(); e.preventDefault(); return; } if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) { e.preventDefault(); if (e.key === "ArrowDown") { activePredictionIndex = (activePredictionIndex + 1) % items.length; updateActivePrediction(items); } else if (e.key === "ArrowUp") { activePredictionIndex = activePredictionIndex <= 0 ? items.length - 1 : activePredictionIndex - 1; updateActivePrediction(items); } else if (e.key === "Enter" && activePredictionIndex >= 0) { items[activePredictionIndex].click(); } } else if (e.key === "Escape") { togglePaletteVisibility(); e.preventDefault(); } } /** * Updates the active prediction item in the command palette. * @param {HTMLCollectionOf<Element>} items */ function updateActivePrediction(items) { Array.from(items).forEach((item, index) => { item.classList.toggle("active", index === activePredictionIndex); }); } // ========================= // Section: Keyboard Shortcuts // ========================= /** * Registers all keyboard shortcuts. */ function registerKeyboardShortcuts() { document.addEventListener("keydown", function (e) { if (e.altKey && e.key === "p") { e.preventDefault(); insertCustomEditorPaletteButtons(); togglePaletteVisibility(); } }); document.addEventListener("keydown", function (e) { if (e.code === "KeyA" && e.altKey) { showActions(); e.preventDefault(); } }); document.addEventListener("keydown", function (e) { if (e.code === "KeyV" && e.altKey) { showVariables(); e.preventDefault(); } }); document.addEventListener("keydown", function (e) { if (e.ctrlKey && e.code === "KeyD") { toggleToolbar(); e.preventDefault(); } }); } // ========================= // Section: Toolbar & Sidebar // ========================= /** * Toggles the toolbar open/closed. */ function toggleToolbar() { const btn = safeQuery( "div.editor-layout__resize:nth-child(2) > button:nth-child(2)", "toggleToolbar" ); if (btn) btn.click(); } /** * Checks if the palette sidebar is open or closed. * @returns {"opened"|"closed"} */ function getPaletteState() { const paletteElement = safeQuery(".editor-layout__palette", "getPaletteState"); if (!paletteElement) return "closed"; return paletteElement.offsetWidth <= 8 ? "closed" : "opened"; } // ========================= // Section: Feature Functions // ========================= /** * Shows the Actions section in the palette. */ function showActions() { if (getPaletteState() === "closed") { toggleToolbar(); } try { safeQuery( "div.editor-palette__accordion:nth-child(2) > div:nth-child(1) > header:nth-child(1) > div:nth-child(1) > button:nth-child(1) > div:nth-child(1) > div:nth-child(2)", "showActions" )?.click(); } catch (e) { console.warn("Failed to show Actions:", e); } try { safeQuery( '.editor-palette-search__cancel button[type="button"][tabindex="-1"]', "showActions" )?.click(); } catch (e) { console.warn("Failed to cancel search:", e); } } /** * Shows the Variables section in the palette. */ async function showVariables() { if (getPaletteState() === "closed") { toggleToolbar(); await sleep(1000); } for (let i = 0; i < 10; i++) { const el = document.querySelector( 'span.clipped-text.clipped-text--no_wrap.editor-palette-section__header-title[title="Variables"]' ); if (el) { el.click(); return; } await sleep(300); } } /** * Shows the Triggers section in the palette. */ function showTriggers() { if (getPaletteState() === "closed") { toggleToolbar(); } safeQuery( 'span.clipped-text.clipped-text--no_wrap.editor-palette-section__header-title[title="Triggers"]', "showTriggers" )?.click(); } /** * Adds a new variable via the UI. */ async function addVariable() { if (getPaletteState() === "closed") { toggleToolbar(); await sleep(800); } const addButton = safeQuery('div.editor-palette__accordion header button', "addVariable"); if (addButton) { addButton.click(); } else { console.warn("Add Variable button not found"); return; } await sleep(500); const createButton = safeQuery('button[name="create"]', "addVariable"); if (createButton) { createButton.click(); } else { console.warn("Create button not found"); return; } await sleep(500); const confirmButton = safeQuery('div.action-bar--theme_default button:nth-child(2)', "addVariable"); if (confirmButton) { confirmButton.click(); } else { console.warn("Confirm button not found"); } } /** * Deletes unused variables via the UI. */ async function deleteUnusedVariables() { await showVariables(); await sleep(1000); const dropdownMenu = safeQuery("button.action-bar__item--is_menu:nth-child(5)", "deleteUnusedVariables"); if (dropdownMenu) dropdownMenu.click(); await sleep(1000); const duvButton = safeQuery(".dropdown-options.g-scroller button.rio-focus--inset_4px:nth-child(2)", "deleteUnusedVariables"); if (duvButton) duvButton.click(); } /** * Scrolls to a specific line number in the editor. * @param {number} lineNumber */ function scrollToLineNumber(lineNumber) { const lineElements = document.querySelectorAll('.taskbot-canvas-list-node > .taskbot-canvas-list-node__number'); if (lineNumber < 1 || lineNumber > lineElements.length) { console.warn(`Line ${lineNumber} is out of range. Total lines: ${lineElements.length}`); return; } const targetElement = lineElements[lineNumber - 1]; document.querySelectorAll('.line-highlighted').forEach(el => el.classList.remove('line-highlighted')); targetElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); targetElement.classList.add('line-highlighted'); setTimeout(() => { targetElement.classList.remove('line-highlighted'); }, 2000); } /** * Shows the help modal. */ function showHelp() { const modalOverlay = document.createElement('div'); const modal = document.createElement('div'); const modalContent = document.createElement('div'); const closeButton = document.createElement('button'); const signature = document.createElement('div'); modalOverlay.style.position = 'fixed'; modalOverlay.style.top = '0'; modalOverlay.style.left = '0'; modalOverlay.style.width = '100vw'; modalOverlay.style.height = '100vh'; modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; modalOverlay.style.display = 'flex'; modalOverlay.style.justifyContent = 'center'; modalOverlay.style.alignItems = 'center'; modalOverlay.style.zIndex = '1000'; modalOverlay.style.fontSize = '16px'; modal.style.backgroundColor = 'white'; modal.style.padding = '20px'; modal.style.borderRadius = '8px'; modal.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)'; modal.style.maxWidth = '800px'; modal.style.width = '80%'; modal.style.position = 'relative'; let helpContent = "<h3>List of Commands:</h3><ul>"; for (let command in commandsWithAliases) { const { aliases, description } = commandsWithAliases[command]; helpContent += `<li><b>${aliases.join(', ')}</b>: ${description}</li>`; } helpContent += `<li><b>:<i>line</i></b>: Scrolls to a specific line number (e.g. <code>:25</code>)</li>`; helpContent += "</ul>"; helpContent += ` <h4>Keyboard Shortcuts:</h4> <ul> <li><b>Alt + P</b>: Open the command palette</li> <li><b>Alt + V</b>: Show variables</li> <li><b>Alt + A</b>: Show actions</li> </ul> <h4>Clipboard Slots:</h4> <ul> <li>Use the context menu (Tampermonkey menu) to: <ul> <li><code>Copy to Slot 1</code>, <code>Slot 2</code>, <code>Slot 3</code></li> <li><code>Paste from Slot 1</code>, <code>Slot 2</code>, <code>Slot 3</code></li> </ul> </li> <li>You can also use the rocket icons in the top action bar to quickly copy/paste</li> </ul> `; modalContent.innerHTML = helpContent; closeButton.textContent = 'Close'; closeButton.style.marginTop = '10px'; closeButton.style.padding = '8px 16px'; closeButton.style.border = 'none'; closeButton.style.backgroundColor = 'var(--color_background_interactive)'; closeButton.style.color = 'white'; closeButton.style.cursor = 'pointer'; closeButton.style.borderRadius = '4px'; signature.innerHTML = `<a href="https://github.com/Jamir-boop/automationanywhere-improvements.git" target="_blank" style="text-decoration: none; color: #888; font-size: 12px;">made by jamir-boop</a>`; signature.style.position = 'absolute'; signature.style.bottom = '8px'; signature.style.right = '12px'; modal.appendChild(modalContent); modal.appendChild(closeButton); modal.appendChild(signature); modalOverlay.appendChild(modal); document.body.appendChild(modalOverlay); function closeModal() { document.body.removeChild(modalOverlay); } modalOverlay.addEventListener('click', (e) => { if (e.target === modalOverlay) { closeModal(); } }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') { closeModal(); } }); closeButton.addEventListener('click', closeModal); } // ========================= // Section: Custom Palette Buttons // ========================= /** * Updates the active button in the custom palette toolbar. */ function updateActiveButton() { const activeSection = document.querySelector( ".editor-palette-section__header--is_active .clipped-text__string--for_presentation" )?.innerText; const buttons = document.querySelectorAll(".customActionVariableButton"); buttons.forEach((button) => { if (button.textContent === activeSection) { button.classList.add("buttonToolbarActive"); } else { button.classList.remove("buttonToolbarActive"); } }); } /** * Inserts custom palette buttons for Variables, Actions, and Triggers. */ function insertCustomEditorPaletteButtons() { if (document.getElementById("customActionVariableButtons")) { return; } const containerDiv = document.createElement("div"); containerDiv.id = "customActionVariableButtons"; const variableButton = document.createElement("button"); variableButton.className = "customActionVariableButton"; variableButton.textContent = "Variables"; variableButton.onclick = function () { showVariables(); updateActiveButton(); }; const actionButton = document.createElement("button"); actionButton.className = "customActionVariableButton"; actionButton.textContent = "Actions"; actionButton.onclick = function () { showActions(); updateActiveButton(); }; const triggerButton = document.createElement("button"); triggerButton.className = "customActionVariableButton"; triggerButton.textContent = "Triggers"; triggerButton.onclick = function () { showTriggers(); updateActiveButton(); }; containerDiv.appendChild(variableButton); containerDiv.appendChild(actionButton); containerDiv.appendChild(triggerButton); const palette = safeQuery(".editor-layout__palette", "insertCustomEditorPaletteButtons"); if (palette) { palette.appendChild(containerDiv); } if (!document.getElementById("customActionVariableButtons-style")) { const style = document.createElement("style"); style.id = "customActionVariableButtons-style"; style.textContent = ` #customActionVariableButtons { display: flex; width: 100%; height: 38px !important; background: white; } #customActionVariableButtons button { all: unset; font-size: .85rem; font-weight: 300; cursor: pointer; margin: 4px; border-radius: 5px; border: 1px solid transparent; background-color: transparent; color: #3c5e83; flex-grow: 1; text-align: center; transition: background-color 0.3s; } #customActionVariableButtons button:hover { background-color: #dae9f3; } .buttonToolbarActive { border: 1px solid #3c5e83 !important; text-shadow: 0.5px 0 0 #3c5e83 , -0.01px 0 0 #3c5e83 !important; } .editor-palette.g-box-sizing_border-box { margin-top: 38px; } `; document.head.appendChild(style); } } // ========================= // Section: Universal Copy/Paste // ========================= /** * Generates a random emoji string for UID. * @returns {string} */ function generateEmojiString() { const emojis = [ "😀", "😃", "😄", "😁", "😆", "😅", "😂", "🤣", "😊", "😇", "🙂", "🙃", "😉", "😌", "😍", "🥰", "😘", "😗", "😙", "😚", "😋", "😛", "😝", "😜", "🤪", "🤨", "🧐", "🤓", "😎", "🤩", "🥳", "😏", "😒", "😞", "😔", "😟", "😕", "🙁", "😣", "😖", "😫", "😩", "🥺", "😢", "😭", "😤", "😠", "😡", "🤬", "🤯", "😳", "🥵", "🥶", "😱", "😨", "😰", "😥", "😓", "🤗", "🤔", "🤭", "🤫", "🤥", "😶", "😐", "😑", "😬", "🙄", "😯", "😦", "😧", "😮", "😲", "🥱", "😴", "🤤", "😪", "😵", "🤐", "🥴", "🤢", "🤮", "🤧", "😷", "🤒", "🤕", "🤑", "🤠", "😈", "👿", "👹", "👺", "🤡", "💩", "👻", "💀", "☠️", "👽", "👾", "🤖", "🎃", "😺", "😸", "😹", "😻", "😼", "😽", "🙀", "😿", "😾" ]; let uniqueString = ""; for (let i = 0; i < 10; i++) { uniqueString += emojis[Math.floor(Math.random() * emojis.length)]; } return uniqueString; } /** * Recursively clears sensitive fields in an object. * @param {object} obj */ function clearSensitiveFields(obj) { if (!obj || typeof obj !== "object") return; for (const key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) continue; if (key === "blob" || key === "thumbnailMetadataPath" || key === "screenshotMetadataPath") { obj[key] = ""; } else if (typeof obj[key] === "object" && obj[key] !== null) { clearSensitiveFields(obj[key]); } } } /** * Cleans Automation Anywhere node JSON by removing sensitive metadata paths and blobs. * @param {string} jsonString - The input JSON string. * @returns {string} - The cleaned, minified JSON string. */ function cleanAutomationAnywhereJson(jsonString) { let data; try { data = JSON.parse(jsonString); } catch (e) { console.error("Invalid JSON input", e); return jsonString; } if (!Array.isArray(data.nodes)) return JSON.stringify(data); for (const node of data.nodes) { if (!Array.isArray(node.attributes)) continue; for (const attr of node.attributes) { if (attr.value && typeof attr.value === "object") { clearSensitiveFields(attr.value); } } } return JSON.stringify(data); } /** * Copies data to the specified clipboard slot. * @param {number} slot */ function copyToSlot(slot) { const copyButton = safeQuery(".aa-icon-action-clipboard-copy--shared", "copyToSlot"); if (copyButton) { copyButton.click(); const globalClipboardJSON = localStorage.getItem('globalClipboard'); try { const clipboardData = JSON.parse(globalClipboardJSON); clipboardData.uid = "🔥🔥🔥"; GM_setValue(`universalClipboardSlot${slot}`, JSON.stringify(clipboardData)); } catch (error) { console.error("Failed to copy data to slot:", error); } } } /** * Pastes data from the specified clipboard slot. * @param {number} slot */ function pasteFromSlot(slot) { const clipboardData = GM_getValue(`universalClipboardSlot${slot}`); if (!clipboardData) { return; } let emojiUid = generateEmojiString(); let modifiedData = clipboardData.replace(/🔥🔥🔥/g, emojiUid); // Clean the JSON before pasting let cleanedData = cleanAutomationAnywhereJson(modifiedData); localStorage.setItem('globalClipboard', cleanedData); localStorage.setItem('globalClipboardUid', `"${emojiUid}"`); const pasteButton = safeQuery(".aa-icon-action-clipboard-paste--shared", "pasteFromSlot"); if (pasteButton) { setTimeout(() => { pasteButton.click(); }, 500); } } function universalCopyCommandPalette() { const btn = document.querySelector(".universalCopy"); if (btn) { btn.click(); } else { universalCopy(); // fallback } } function universalPasteCommandPalette() { const btn = document.querySelector(".universalPaste"); if (btn) { btn.click(); } else { universalPaste(); // fallback } } /** * Inserts universal copy/paste buttons into the action bar. * @param {number} attempt */ function insertUniversalCopyPasteButtons(attempt = 1) { setTimeout(() => { const actionBar = document.querySelector('.action-bar--theme_info'); if (actionBar && !actionBar.querySelector('.universalCopy')) { const separator = document.createElement('div'); separator.className = 'action-bar__separator'; actionBar.appendChild(separator); // Universal Copy button const copyButton = document.createElement('button'); copyButton.className = 'universalCopy rio-focus rio-focus--inset_0 rio-focus--border-radius_4px rio-focus--has_element-focus-visible rio-bare-button g-reset-element rio-bare-button--is_interactive rio-bare-button--rio_interactive-softest rio-bare-button--is_parent rio-bare-button--is_clickable rio-bare-button--size_14px rio-bare-button--is_square rio-bare-button--square_26x26 action-bar__item action-bar__item--is_action taskbot-editor__toolbar__action'; copyButton.innerHTML = `<span class="icon fa fa-rocket icon--block"></span>`; copyButton.title = 'Universal Copy'; copyButton.onclick = universalCopy; actionBar.appendChild(copyButton); // Universal Paste button const pasteButton = document.createElement('button'); pasteButton.className = 'universalPaste rio-focus rio-focus--inset_0 rio-focus--border-radius_4px rio-focus--has_element-focus-visible rio-bare-button g-reset-element rio-bare-button--is_interactive rio-bare-button--rio_interactive-softest rio-bare-button--is_parent rio-bare-button--is_clickable rio-bare-button--size_14px rio-bare-button--is_square rio-bare-button--square_26x26 action-bar__item action-bar__item--is_action taskbot-editor__toolbar__action'; pasteButton.innerHTML = `<span class="icon fa fa-rocket icon--block" style="transform: rotate(180deg);"></span>`; pasteButton.title = 'Universal Paste'; pasteButton.onclick = universalPaste; actionBar.appendChild(pasteButton); } else if (attempt < 3) { insertUniversalCopyPasteButtons(attempt + 1); } }, 1000 * attempt); } /** * Universal copy action. */ function universalCopy() { const copyBtn = safeQuery(".aa-icon-action-clipboard-copy--shared", "universalCopy"); if (copyBtn) copyBtn.click(); const globalClipboardJSON = localStorage.getItem('globalClipboard'); let globalClipboard = {}; try { globalClipboard = JSON.parse(globalClipboardJSON); } catch (e) { console.error("Error parsing JSON:", e); return; } globalClipboard.uid = "🔥🔥🔥"; GM_setValue('universalClipboard', JSON.stringify(globalClipboard)); } /** * Universal paste action. */ function universalPaste() { const copyBtn = safeQuery(".aa-icon-action-clipboard-copy--shared", "universalPaste"); if (copyBtn) copyBtn.click(); let universalClipboard = GM_getValue('universalClipboard'); if (universalClipboard) { let emojiUid = generateEmojiString(); universalClipboard = universalClipboard.replace(/'/g, '"'); universalClipboard = universalClipboard.replace(/🔥🔥🔥/g, emojiUid); // Clean the JSON before pasting let cleanedData = cleanAutomationAnywhereJson(universalClipboard); localStorage.setItem("globalClipboard", cleanedData); localStorage.setItem("globalClipboardUid", `"${emojiUid}"`); } setTimeout(() => { const pasteBtn = safeQuery(".aa-icon-action-clipboard-paste--shared", "universalPaste"); if (pasteBtn) pasteBtn.click(); }, 1000); } // ========================= // Section: Redirect Utility // ========================= /** * Redirects to a given path within the Automation Anywhere domain. * @param {string} targetPath */ function redirectToPath(targetPath) { const currentUrl = window.location.href; const pattern = /^(https:\/\/[^\/]*\.automationanywhere\.digital)/; const match = currentUrl.match(pattern); if (match) { const baseUrl = match[1]; const newUrl = baseUrl + targetPath; window.location.href = newUrl; } else { console.error("Pattern didn't match. The URL might not be in the expected format."); } } function redirectToPublicRepository() { redirectToPath('/#/bots/repository/public/'); } function redirectToPrivateRepository() { redirectToPath('/#/bots/repository/private/'); } function redirectToActivityHistorical() { redirectToPath('/#/activity/historical/'); } function redirectToInProgress() { redirectToPath('/#/activity/inprogress/'); } function redirectToAuditLog() { redirectToPath('/#/audit'); } function redirectToAdminUsers() { redirectToPath('/#/admin/users/'); } function redirectToAdminRoles() { redirectToPath('/#/admin/roles/'); } function redirectToAdminDevices() { redirectToPath('/#/devices/'); } function redirectToHome() { redirectToPath('/#/dashboard/home/overview'); } // ========================= // Section: Command Palette Insertion // ========================= /** * Inserts the command palette into the DOM. * @param {number} retryCount */ function insertCommandPalette(retryCount = 0) { if (document.querySelector("#commandPalette")) { return; } const containerDiv = document.createElement("div"); containerDiv.id = "commandPalette"; containerDiv.className = "command_palette--hidden"; containerDiv.innerHTML = ` <input type="text" id="commandInput" placeholder="Enter command..."> <div id="commandPredictions" class="command_predictions"></div> `; document.body.appendChild(containerDiv); if (!document.getElementById("commandPalette-style")) { const style = document.createElement("style"); style.id = "commandPalette-style"; style.type = "text/css"; style.appendChild(document.createTextNode(` #commandPalette * { font-size: 1.15rem; font-family: Museo Sans,sans-serif; } #commandPalette { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: white; border-radius: 10px 10px 0 0; display: flex; flex-direction: column; align-items: center; min-width: 30vw; max-width: 80vw; width: 600px; z-index: 99999; box-shadow: 0px 0px 0px 5000px #00000054; } #commandInput, #commandInput:focus-visible, #commandInput:active { unset: all; padding: 10px; width: 93%; margin-bottom: 10px; border: 2px solid transparent; border-radius: 5px; } #commandPalette:focus, #commandPalette:active { border-color: orange; } #commandPredictions { position: absolute; top: 100%; left: 0; width: 100%; background-color: white; box-shadow: 0 4px 8px rgba(0,0,0,0.1); border-radius: 0 0 10px 10px; max-height: 200px; overflow-y: auto; z-index: 100000; } .command_prediction-item.active { background-color: #f0f0f0; } .command_prediction-item strong { font-weight: bold; } .command_prediction-item { padding: 8px; cursor: pointer; border-bottom: 1px solid #eee; } .command_prediction-item:hover, .command_prediction-item.active { background-color: #f0f0f0; } @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, -50%) scale(0.85); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } @keyframes fadeOut { from { opacity: 1; transform: translate(-50%, -50%) scale(1); } to { opacity: 0; transform: translate(-50%, -50%) scale(0.95); } } .command_palette--visible { display: block; animation: fadeIn 0.25s ease-out forwards; } .command_palette--hidden { animation: fadeOut 0.25s ease-out forwards; display: none; pointer-events: none; opacity: 0; z-index: -1; } `)); document.head.appendChild(style); } setupCommandInputEventListeners(); if (!document.querySelector("#commandPalette")) { if (retryCount < 5) { setTimeout(() => insertCommandPalette(retryCount + 1), 3000); } else { console.error("Failed to insert command palette after 5 retries."); } } } /** * Exports the currently copied action as JSON to the user's clipboard, * with uid set to 🔥🔥🔥 (for universal sharing). */ async function exportActionToClipboard() { try { // Use universalCopy to set GM storage with 🔥🔥🔥 as uid universalCopy(); // Wait a short moment to ensure GM_setValue is complete await sleep(200); const universalClipboard = GM_getValue('universalClipboard'); if (!universalClipboard) { return; } await navigator.clipboard.writeText(universalClipboard); } catch (e) { console.warn("Failed to export action to clipboard:", e); } } function importActionFromJson() { // Modal setup const overlay = document.createElement('div'); overlay.style.position = 'fixed'; overlay.style.top = '0'; overlay.style.left = '0'; overlay.style.width = '100vw'; overlay.style.height = '100vh'; overlay.style.background = 'rgba(0,0,0,0.5)'; overlay.style.zIndex = '100000'; overlay.style.display = 'flex'; overlay.style.justifyContent = 'center'; overlay.style.alignItems = 'center'; const modal = document.createElement('div'); modal.style.background = 'white'; modal.style.padding = '24px'; modal.style.borderRadius = '8px'; modal.style.maxWidth = '600px'; modal.style.width = '90%'; modal.style.boxShadow = '0 4px 16px rgba(0,0,0,0.15)'; modal.style.display = 'flex'; modal.style.flexDirection = 'column'; modal.style.alignItems = 'stretch'; const label = document.createElement('label'); label.textContent = "Paste Automation Anywhere Action JSON:"; label.style.marginBottom = '8px'; const textarea = document.createElement('textarea'); textarea.style.width = '100%'; textarea.style.height = '180px'; textarea.style.marginBottom = '12px'; textarea.style.fontFamily = 'monospace'; textarea.style.fontSize = '1rem'; const buttonRow = document.createElement('div'); buttonRow.style.display = 'flex'; buttonRow.style.justifyContent = 'flex-end'; buttonRow.style.gap = '8px'; const importBtn = document.createElement('button'); importBtn.textContent = "Import & Paste"; importBtn.style.padding = '8px 16px'; importBtn.style.background = 'var(--color_background_interactive, #3c5e83)'; importBtn.style.color = 'white'; importBtn.style.border = 'none'; importBtn.style.borderRadius = '4px'; importBtn.style.cursor = 'pointer'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = "Cancel"; cancelBtn.style.padding = '8px 16px'; cancelBtn.style.background = '#ccc'; cancelBtn.style.color = '#222'; cancelBtn.style.border = 'none'; cancelBtn.style.borderRadius = '4px'; cancelBtn.style.cursor = 'pointer'; buttonRow.appendChild(cancelBtn); buttonRow.appendChild(importBtn); modal.appendChild(label); modal.appendChild(textarea); modal.appendChild(buttonRow); overlay.appendChild(modal); document.body.appendChild(overlay); textarea.focus(); function closeModal() { document.body.removeChild(overlay); } cancelBtn.onclick = closeModal; overlay.onclick = (e) => { if (e.target === overlay) closeModal(); }; document.addEventListener('keydown', function escListener(e) { if (e.key === 'Escape') { closeModal(); document.removeEventListener('keydown', escListener); } }); importBtn.onclick = async function () { let input = textarea.value.trim(); if (!input) { return; } try { // Validate JSON JSON.parse(input); } catch (e) { return; } // Set GM storage for universal paste GM_setValue('universalClipboard', input); closeModal(); // Wait a short moment to ensure GM_setValue is complete await sleep(200); universalPaste(); }; } // ========================= // Section: Initialization // ========================= /** * Runs all startup functions, using MutationObserver for palette and action bar. */ function initialize() { if (!document.querySelector("#commandPalette")) { insertCommandPalette(); } if (!document.getElementById("customActionVariableButtons")) { insertCustomEditorPaletteButtons(); } insertUniversalCopyPasteButtons(); removeInlineWidth(); // Only set up listeners and intervals once if (!initialized) { registerKeyboardShortcuts(); updateActiveButtonIntervalId = setInterval(updateActiveButton, 1000); initialized = true; // Observe for SPA navigation changes let lastHref = document.location.href; setInterval(function () { const currentHref = document.location.href; if (lastHref !== currentHref) { lastHref = currentHref; insertCommandPalette(); insertCustomEditorPaletteButtons(); insertUniversalCopyPasteButtons(); removeInlineWidth(); } }, 5000); } } /** * Removes inline width from the navigation panel. */ function removeInlineWidth() { const nav = document.querySelector('.main-layout__navigation'); const pathfinderCollapsed = document.querySelector('.pathfinder--is_collapsed'); if (pathfinderCollapsed) { if (nav?.style?.width) { nav.style.removeProperty('width'); } return; } const collapseButton = document.querySelector('button[aria-label="Collapse"]'); if (collapseButton) { collapseButton.click(); setTimeout(() => { if (nav?.style?.width) { nav.style.removeProperty('width'); } }, 500); } else { console.warn('Collapse button not found'); } } // ========================= // Section: Tampermonkey Menu Commands // ========================= // Register menu commands for selecting copy/paste slots GM_registerMenuCommand("Copy to Slot 1", () => copyToSlot(1)); GM_registerMenuCommand("Copy to Slot 2", () => copyToSlot(2)); GM_registerMenuCommand("Copy to Slot 3", () => copyToSlot(3)); GM_registerMenuCommand("Paste from Slot 1", () => pasteFromSlot(1)); GM_registerMenuCommand("Paste from Slot 2", () => pasteFromSlot(2)); GM_registerMenuCommand("Paste from Slot 3", () => pasteFromSlot(3)); // ========================= // Section: DOM Ready // ========================= function callInitializeRepeatedly(times = 3, interval = 5000) { let count = 0; const intervalId = setInterval(() => { initialize(); count++; if (count >= times) { clearInterval(intervalId); } }, interval); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", () => callInitializeRepeatedly()); } else { callInitializeRepeatedly(); } })();