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-06 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @license      JazzMedo
// @name         TryHackMe Copy to Clipboard and add notes
// @version      2025-01-06
// @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://greasyfork.org/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.top = '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();

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