TryHackMe Copy to Clipboard and add notes

this script will allow you to copy the content from the room and save notes at the bottom right of the page

当前为 2025-01-19 提交的版本,查看 最新版本

// ==UserScript==
// @license      JazzMedo
// @name         TryHackMe Copy to Clipboard and add notes
// @version      1.0.2
// @description  this script will allow you to copy the content from the room and save notes at the bottom right of the page 
// @author       JazzMedo
// @match        https://tryhackme.com/r/*
// @include      https://tryhackme.com/r/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tryhackme.com
// @grant        none
// @namespace https://gf.qytechs.cn/users/1420266
// ==/UserScript==

(function () {
    'use strict';

    setTimeout(() => {
        (function () {
            // Select all elements with data-sentry-component="AccordionDetails"
            const accordionDetailsList = document.querySelectorAll('[data-sentry-component="AccordionDetails"]');

            // Function to get text excluding images and handling links
            const getTextExcludingImages = (element) => {
                let textContent = '';

                // Loop through the child nodes of the element
                Array.from(element.childNodes).forEach((child) => {
                    // Skip if the child or its parent is inside a <pre> tag
                    const isInsidePre = (node) => {
                        while (node && node !== element) {
                            if (node.tagName === 'PRE' || node.tagName === 'pre') {
                                return true;
                            }
                            node = node.parentNode;
                        }
                        return false;
                    };

                    if (isInsidePre(child.nodeType === Node.TEXT_NODE ? child.parentNode : child)) {
                        return;
                    }

                    // Only consider text nodes or elements that aren't images
                    if (child.nodeType === Node.TEXT_NODE) {
                        let text = child.textContent.trim();
                        if (text) {
                            // Check if the parent element is a <p> tag
                            const parentElement = child.parentElement;
                            const isParagraph = parentElement && parentElement.tagName === 'P';

                            // Add newline if the text ends with a period in a <p> tag
                            if ((isParagraph && text.trim(" ").endsWith('.')) || text.endsWith('?') || text.endsWith(':')) {
                                textContent += text + '\n\n';
                            } else {
                                textContent += text;
                            }
                        }
                    }
                    else if (child.nodeType === Node.ELEMENT_NODE && child.tagName !== 'IMG') {
                        // Skip <pre> tags and their contents

                        if (child.tagName === 'PRE' || child.tagName === 'pre') {
                            return;
                        }

                        // If it's an element and not an image, recurse into it
                        if (child.tagName === 'A' || child.tagName === 'a') {
                            // If it's a link, add the text and a newline after it
                            textContent += ' ' + child.textContent.trim() + ' ';
                        }
                        else if (child.tagName === 'B' || child.tagName === 'b') {
                            // If the element is a <b> tag, add the text and a newline after the paragraph
                            textContent += child.textContent.trim() + '\n\n';
                        } else if (child.tagName === 'span' || child.tagName === 'SPAN') {
                            // If the element is a <b> tag, add the text and a newline after the paragraph
                            textContent += " " + child.textContent.trim() + ' ';
                        }
                        else if (child.tagName === 'UL' || child.tagName === 'ul') {
                            // Add newline after a <ul> tag
                            textContent += getTextExcludingImages(child) + '\n';
                        } else if (child.tagName === 'LI' || child.tagName === 'li') {
                            // Add newline after a <li> tag
                            textContent += child.textContent.trim() + '\n';
                        } else if (child.tagName === 'P' || child.tagName === 'p') {
                            // If <p> contains <b>, add \n\n before <p>
                            if (child.querySelector('b') || child.querySelector('B')) {
                                textContent += '\n\n' + getTextExcludingImages(child);
                            } else {
                                textContent += getTextExcludingImages(child) + '\n\n';
                            }
                        } else if (child.tagName === 'TABLE' || child.tagName === 'table') {
                            // Format the table content
                            textContent += formatTable(child) + '\n\n'; // Call a new function to format the table
                        } else if (child.tagName === 'DIV' || child.tagName === 'div') {
                            // Add double newlines before and after the <div> content
                            textContent += '\n\n' + getTextExcludingImages(child) + '\n\n';
                        } else if (child.tagName === 'H2' || child.tagName === 'h2') {
                            // Add newline after <h2> tag
                            textContent += child.textContent.trim() + '\n\n';
                        } else if (child.tagName === 'code' || child.tagName === 'CODE') {
                            // Add newline after <h2> tag
                            textContent += ' `' + child.textContent.trim() + '` ';
                        } else {
                            // Otherwise, continue recursively
                            textContent += getTextExcludingImages(child);
                        }
                    }
                });

                // Filter excessive newlines (replace \n\n\n with \n\n)
                textContent = textContent.replace(/\n{3,}/g, '\n\n');

                return textContent;
            };

            // New function to format the table
            function formatTable(table) {
                let formattedTable = '----------------------------\n';
                const rows = table.querySelectorAll('tr');
                rows.forEach((row) => {
                    const cells = row.querySelectorAll('th, td');
                    const rowContent = Array.from(cells).map(cell => cell.textContent.trim()).join(' | '); // Join cell content with a separator
                    formattedTable += '| ' + rowContent + ' |\n'; // Add a newline after each row
                });

                // Add a newline after each row for separation
                formattedTable += '----------------------------\n'; // Ensure there's a newline after the last row

                return formattedTable;
            }

            // Loop through all the matched elements
            accordionDetailsList.forEach((accordionDetails) => {
                // Create a button for each AccordionDetails element
                const button = document.createElement('button');
                // Replace text with SVG icon
                button.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
            </svg>`;
                button.title = 'Copy Text Only';
                button.style.position = 'absolute';
                button.style.bottom = '10px';
                button.style.right = '10px'; // Position the button in the top-right corner
                // button.style.zIndex = '1000';
                button.style.padding = '8px 8px 4px 8px';  // Make the button more square-shaped for the icon
                button.style.borderRadius = '5px';
                button.style.backgroundColor = '#47acee'; // Green background
                button.style.color = 'white';
                button.style.border = 'none';
                button.style.cursor = 'pointer';
                button.style.transition = 'all ease-in-out 0.3s';

                // Position the region element relative to show the button correctly
                accordionDetails.style.position = 'relative';

                // Add the button to the accordion details element
                accordionDetails.appendChild(button);

                // Flag to track whether the button was already clicked
                let isClicked = false;

                // Set up button click event to copy the text
                button.addEventListener('click', () => {
                    if (isClicked) return; // Prevent further clicks if already clicked

                    isClicked = true; // Mark as clicked

                    let textContent = getTextExcludingImages(accordionDetails);


                    // Use the Clipboard API to copy text
                    if (textContent) {
                        navigator.clipboard.writeText(textContent).then(() => {
                            // Change button icon to checkmark when copied
                            button.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="20 6 9 17 4 12"></polyline>
                        </svg>`;
                            button.style.backgroundColor = '#4CAF50';
                            setTimeout(() => {
                                // Reset to copy icon
                                button.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                            <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                            </svg>`;
                                button.style.backgroundColor = '#47acee';
                                isClicked = false;
                            }, 1000);
                        }).catch((err) => {
                            console.error('Failed to copy text: ', err);
                            isClicked = false; // Reset flag in case of error
                        });
                    }
                });
            });


            // Inject CSS into the document
            const style = document.createElement('style');
            style.innerHTML = `
    pre {
        position: relative; /* Ensure the button is positioned relative to the pre block */
    }
    .copy-button {
        position: absolute;
        bottom: 10px;
        right: 10px;

        background-color: #47acee; /* Dark yellow background */
        color: white;
        border: none;
        padding: 8px 8px 4px;
        cursor: pointer;
        border-radius: 5px;
        transition: all ease-in-out 0.2s; /* Animation for button press */

    }
    .copy-button:hover {

        background-color: #8cd1ff; /* Darker green on hover */
    }
    .copy-button:active {
        transform: scale(0.95); /* Slightly shrink the button on click */
    }
`;
            document.head.appendChild(style);

            document.querySelectorAll('pre code').forEach(block => {
                const copyButton = document.createElement('button');
                copyButton.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
            </svg>`;
                copyButton.className = 'copy-button'; // Add class for styling
                copyButton.title = 'Copy Commands Only';
                block.parentElement.style.position = 'relative'; // Ensure the parent is positioned
                block.parentElement.appendChild(copyButton);

                copyButton.addEventListener('click', () => {
                    const lines = block.innerText.split('\n');
                    const commands = lines.map(line => {
                        if (line.includes('$ ')) {
                            const parts = line.split('$ ');
                            return parts.length > 1 ? parts[1] : '';
                        } else if (line.includes('>')) {
                            const parts = line.split('>');
                            return parts.length > 1 ? parts[1].trim() : '';
                        }
                        return '';
                    }).filter(command => command !== '').join('\n');

                    navigator.clipboard.writeText(commands).then(() => {
                        const originalText = copyButton.innerHTML;
                        copyButton.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="20 6 9 17 4 12"></polyline>
                        </svg>`;
                        copyButton.style.backgroundColor = '#4CAF50';
                        setTimeout(() => {
                            copyButton.innerHTML = originalText;
                            copyButton.style.backgroundColor = '#47acee';
                        }, 2000);
                    });
                });
            });


            // تعديل مكان الزر العائم ليكون في الزاوية السفلى اليسرى// تعديل مكان الزر العائم ليكون في الزاوية السفلى اليسرى
            function createCommandPopupUI() {
                if (document.querySelector('#command-popup-ui')) return;

                const floatingButton = document.createElement('button');
                floatingButton.id = 'floating-button';
                floatingButton.innerHTML = `+`;
                floatingButton.style.position = 'fixed';
                floatingButton.style.fontSize = '2rem';
                floatingButton.style.bottom = '20px';
                floatingButton.style.left = '20px'; // استخدام left بدلاً من right
                floatingButton.style.width = '33px';
                floatingButton.style.height = '33px';
                floatingButton.style.borderRadius = '5px';
                floatingButton.style.border = 'none';
                floatingButton.style.backgroundColor = '#61dafb';
                floatingButton.style.color = 'white';
                floatingButton.style.fontSize = '24px';
                floatingButton.style.cursor = 'pointer';
                floatingButton.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
                floatingButton.style.zIndex = '9999';
                floatingButton.style.transition = 'all ease-in-out 0.3s';

                const popup = document.createElement('div');
                popup.id = 'command-popup-ui';
                popup.style.position = 'fixed';
                popup.style.bottom = '80px';
                popup.style.left = '20px'; // تغيير الموضع إلى يسار الصفحة
                popup.style.width = '400px';
                popup.style.backgroundColor = '#282c34';
                popup.style.color = 'white';
                popup.style.padding = '15px';
                popup.style.borderRadius = '8px';
                popup.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
                popup.style.display = 'none';
                popup.style.zIndex = '9999';

                const input = document.createElement('input');
                input.type = 'text';
                input.placeholder = 'Write you command here ...';
                input.style.width = 'calc(100% - 50px)';
                input.style.padding = '10px';
                input.style.borderRadius = '4px';
                input.style.border = 'none';
                input.style.marginRight = '10px';

                // Add keypress event listener for Enter key
                input.addEventListener('keypress', (event) => {
                    if (event.key === 'Enter') {
                        const command = input.value.trim();
                        if (command) {
                            const originalContent = runButton.innerHTML;
                            runButton.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                <polyline points="20 6 9 17 4 12"></polyline>
                            </svg>`;
                            runButton.style.transform = 'scale(0.95)';

                            output.appendChild(createCommandElement(command));
                            saveCommand(command);
                            input.value = '';

                            setTimeout(() => {
                                runButton.innerHTML = originalContent;
                                runButton.style.transform = 'scale(1)';
                            }, 1000);
                        }
                    }
                });

                const inputContainer = document.createElement('div');
                inputContainer.style.display = 'flex';
                inputContainer.style.alignItems = 'center';
                inputContainer.style.marginBottom = '10px';

                const runButton = document.createElement('button');
                runButton.innerText = "➕";
                runButton.style.backgroundColor = '#61dafb';
                runButton.style.color = '#282c34';
                runButton.style.border = 'none';
                runButton.style.padding = '10px';
                runButton.style.borderRadius = '4px';
                runButton.style.cursor = 'pointer';
                runButton.style.width = '40px';
                runButton.style.height = '38px';

                const output = document.createElement('div');
                output.style.marginTop = '10px';
                output.style.fontSize = '14px';
                output.style.overflowY = 'auto';
                output.style.maxHeight = '200px';
                output.style.transition = 'all 0.3s ease';

                const savedCommands = JSON.parse(localStorage.getItem('commands')) || [];

                function createCommandElement(command) {
                    const commandContainer = document.createElement('div');
                    commandContainer.style.display = 'flex';
                    commandContainer.style.alignItems = 'center';
                    commandContainer.style.marginBottom = '8px';
                    commandContainer.style.animation = 'slideIn 0.3s ease';

                    const commandText = document.createElement('div');
                    commandText.innerHTML = `$ ${command}`;
                    commandText.style.color = '#61dafb';
                    commandText.style.flex = '1';

                    const buttonsContainer = document.createElement('div');
                    buttonsContainer.style.display = 'flex';
                    buttonsContainer.style.gap = '5px';

                    const cmdCopyBtn = document.createElement('button');
                    cmdCopyBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                  <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
              </svg>`;
                    cmdCopyBtn.style.backgroundColor = '#98c379';
                    cmdCopyBtn.style.color = 'white';
                    cmdCopyBtn.style.border = 'none';
                    cmdCopyBtn.style.padding = '8px 8px 4px';
                    cmdCopyBtn.style.borderRadius = '4px';
                    cmdCopyBtn.style.cursor = 'pointer';
                    cmdCopyBtn.style.fontSize = '12px';
                    cmdCopyBtn.style.marginLeft = '10px';
                    cmdCopyBtn.style.transition = 'transform 0.2s ease';

                    const deleteBtn = document.createElement('button');
                    deleteBtn.innerText = '🗑';
                    deleteBtn.style.backgroundColor = '#e06c75';
                    deleteBtn.style.color = 'white';
                    deleteBtn.style.border = 'none';
                    deleteBtn.style.padding = '5px 10px';
                    deleteBtn.style.borderRadius = '4px';
                    deleteBtn.style.cursor = 'pointer';
                    deleteBtn.style.fontSize = '12px';
                    deleteBtn.style.transition = 'transform 0.2s ease';

                    cmdCopyBtn.addEventListener('click', () => {
                        navigator.clipboard.writeText(command);
                        const originalSvg = cmdCopyBtn.innerHTML;
                        cmdCopyBtn.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                              <polyline points="20 6 9 17 4 12"></polyline>
                          </svg>`;
                        cmdCopyBtn.style.transform = 'scale(0.95)';
                        setTimeout(() => {
                            cmdCopyBtn.style.transform = 'scale(1)';
                            cmdCopyBtn.innerHTML = originalSvg;
                        }, 1000);
                    });

                    deleteBtn.addEventListener('click', () => {
                        const originalContent = commandContainer.innerHTML;
                        commandContainer.style.backgroundColor = '#ff000015';
                        commandContainer.innerHTML = `
          <div style="display: flex; align-items: center; justify-content: space-between; width: 100%;">
            <span style="color: #e06c75;">Are you sure you want to delete this command?</span>
            <div style="display: flex; gap: 8px;">
              <button id="confirm-delete" style="background: #e06c75; border: none; color: white; padding: 5px 10px; border-radius: 4px; cursor: pointer;">Yes</button>
              <button id="cancel-delete" style="background: #98c379; border: none; color: white; padding: 5px 10px; border-radius: 4px; cursor: pointer;">No</button>
            </div>
          </div>
        `;

                        const confirmBtn = commandContainer.querySelector('#confirm-delete');
                        const cancelBtn = commandContainer.querySelector('#cancel-delete');

                        confirmBtn.addEventListener('click', () => {
                            commandContainer.style.animation = 'slideOut 0.3s ease';
                            setTimeout(() => {
                                commandContainer.remove();
                                const index = savedCommands.indexOf(command);
                                if (index > -1) {
                                    savedCommands.splice(index, 1);
                                    localStorage.setItem('commands', JSON.stringify(savedCommands));
                                }
                            }, 280);
                        });

                        cancelBtn.addEventListener('click', () => {
                            commandContainer.style.backgroundColor = 'transparent';
                            commandContainer.innerHTML = originalContent;
                        });
                    });

                    buttonsContainer.appendChild(cmdCopyBtn);
                    buttonsContainer.appendChild(deleteBtn);
                    commandContainer.appendChild(commandText);
                    commandContainer.appendChild(buttonsContainer);
                    return commandContainer;
                }

                savedCommands.forEach(command => {
                    output.appendChild(createCommandElement(command));
                });

                function saveCommand(command) {
                    savedCommands.push(command);
                    localStorage.setItem('commands', JSON.stringify(savedCommands));
                }

                runButton.addEventListener('click', () => {
                    const command = input.value.trim();
                    if (command) {
                        const originalContent = runButton.innerHTML;
                        runButton.innerHTML = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                              <polyline points="20 6 9 17 4 12"></polyline>
                          </svg>`;
                        runButton.style.transform = 'scale(0.95)';

                        output.appendChild(createCommandElement(command));
                        saveCommand(command);
                        input.value = '';

                        setTimeout(() => {
                            runButton.innerHTML = originalContent;
                            runButton.style.transform = 'scale(1)';
                        }, 1000);
                    }
                });

                floatingButton.addEventListener('click', () => {
                    if (popup.style.display === 'none') {
                        popup.style.display = 'block';
                        popup.style.opacity = '0';
                        popup.style.transform = 'scale(0.95) translateY(20px)';
                        floatingButton.innerHTML = `-`;
                        floatingButton.style.backgroundColor = '#ffcdd2';
                        setTimeout(() => {
                            popup.style.opacity = '1';
                            popup.style.transform = 'scale(1) translateY(0)';
                        }, 0);
                    } else {
                        popup.style.opacity = '0';
                        popup.style.transform = 'scale(0.95) translateY(20px)';
                        floatingButton.innerHTML = `+`;
                        floatingButton.style.backgroundColor = '#61dafb';
                        setTimeout(() => {
                            popup.style.display = 'none';
                        }, 300);
                    }
                });

                const style = document.createElement('style');
                style.textContent = `
      @keyframes slideIn {
        from { opacity: 0; transform: translateY(10px); }
        to { opacity: 1; transform: translateY(0); }
      }

      @keyframes slideOut {
        from { opacity: 1; transform: translateX(0); }
        to { opacity: 0; transform: translateX(-100%); }
      }

      @keyframes popIn {
        0% { transform: scale(0) rotate(-180deg); opacity: 0; }
        100% { transform: scale(1) rotate(0deg); opacity: 1; }
      }

      #floating-button {
        transition: transform 0.3s ease;
        animation: popIn 0.5s ease;
      }

      #floating-button:hover {
        transform: scale(1.1);
      }

      #command-popup-ui {
        transition: opacity 0.3s ease, transform 0.3s ease;
        transform-origin: bottom left;
      }
    `;
                document.head.appendChild(style);

                inputContainer.appendChild(input);
                inputContainer.appendChild(runButton);
                popup.appendChild(inputContainer);
                popup.appendChild(output);

                document.body.appendChild(floatingButton);
                document.body.appendChild(popup);
            }

            createCommandPopupUI();

        })();
    }, 2000); // Delay the execution by 2 seconds
    // Your code here...
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址