G00DB0Y / Expasy Data Extract (Auto Submit, Excel Table)

// ==UserScript==
// @name         Expasy Data Extract (Auto Submit, Excel Table)
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Collect specific protein data from Expasy ProtParam results, display it in an elegant table with copy functionality and notification, and include a fixed title. Molecular weight is divided by 1000 before displaying.
// @author       Faisal Ahmed
// @license      MIT
// @match        https://web.expasy.org/protparam/*
// @match        https://web.expasy.org/cgi-bin/protparam/protparam*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Log messages to help debugging
    console.log("Tampermonkey script started!");

    // Function to create and show the table for displaying collected data
    function createExcelTable(proteinData) {
        console.log("Creating Excel-like table...");

        // Create a container div for the table
        const tableContainer = document.createElement('div');
        tableContainer.style.position = 'fixed';
        tableContainer.style.top = '10px';
        tableContainer.style.right = '10px';
        tableContainer.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
        tableContainer.style.color = '#000';
        tableContainer.style.padding = '15px';
        tableContainer.style.borderRadius = '8px';
        tableContainer.style.zIndex = '9999';
        tableContainer.style.width = '450px';
        tableContainer.style.fontSize = '12px'; // Smaller font size
        tableContainer.style.maxHeight = '400px';
        tableContainer.style.overflowY = 'auto';
        tableContainer.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';

        // Create and style the fixed title with blinking rainbow effect
        const fixedTitle = document.createElement('div');
        fixedTitle.textContent = 'Made By Faisal Ahmed';
        fixedTitle.style.fontSize = '14px'; // Slightly smaller title font
        fixedTitle.style.fontWeight = 'bold';
        fixedTitle.style.marginBottom = '10px';
        fixedTitle.style.paddingBottom = '5px';
        fixedTitle.style.borderBottom = '1px solid #ddd';
        fixedTitle.style.textAlign = 'center';
        fixedTitle.style.backgroundClip = 'text';
        fixedTitle.style.webkitBackgroundClip = 'text'; // For Safari and Chrome
        fixedTitle.style.color = 'transparent'; // Hide the original text color
        fixedTitle.style.backgroundImage = 'linear-gradient(to right, violet, indigo, blue, green, yellow, orange, red)';
        fixedTitle.style.animation = 'blink 5s linear infinite';

        // Add CSS for the blinking rainbow effect
        const style = document.createElement('style');
        style.innerHTML = `
            @keyframes blink {
                0% {
                    background-position: 100%;
                }
                50% {
                    background-position: 0%;
                }
                100% {
                    background-position: 100%;
                }
            }
            div {
                background-size: 200%;
            }
        `;
        document.head.appendChild(style);

        tableContainer.appendChild(fixedTitle);

        // Create a table element
        const table = document.createElement('table');
        table.style.width = '100%';
        table.style.borderCollapse = 'collapse';
        table.style.textAlign = 'left';
        table.style.backgroundColor = '#333'; // Darker background color for the table
        table.style.borderRadius = '8px'; // Rounded corners for the table
        table.style.fontSize = '12px'; // Smaller font size for table

        // Create the table header
        const headerRow = document.createElement('tr');
        const headers = ['Protein Length (aa)', 'Molecular weight (kDa)', 'Theoretical pI', 'Instability index', 'Aliphatic index', 'GRAVY'];

        headers.forEach(headerText => {
            const th = document.createElement('th');
            th.textContent = headerText;
            th.style.border = '1px solid #444'; // Darker border for header
            th.style.padding = '10px'; // Reduced padding for a more compact look
            th.style.backgroundColor = '#555'; // Slightly lighter dark color for header
            th.style.color = '#fff'; // White text for better contrast
            th.style.borderRadius = '4px';
            th.style.fontWeight = 'bold';
            headerRow.appendChild(th);
        });

        table.appendChild(headerRow);

        // Create the table row with protein data
        const dataRow = document.createElement('tr');

        for (const key in proteinData) {
            const td = document.createElement('td');

            // Add condition for the "Instability index" column
            if (key === "Instability index") {
                const instabilityValue = parseFloat(proteinData[key]);
                if (!isNaN(instabilityValue)) {
                    if (instabilityValue > 39.9) {
                        td.textContent = `${instabilityValue} (unstable)`;
                    } else {
                        td.textContent = `${instabilityValue} (stable)`;
                    }
                } else {
                    td.textContent = "Nope";
                }
            } else if (key === "Molecular weight") {
                // Divide molecular weight by 1000 and show in kDa
                const molecularWeightValue = parseFloat(proteinData[key]);
                td.textContent = !isNaN(molecularWeightValue) ? `${(molecularWeightValue / 1000).toFixed(3)}` : 'Nope';
            } else {
                td.textContent = proteinData[key] || 'Nope';
            }

            td.style.border = '1px solid #444'; // Darker border for data cells
            td.style.padding = '8px'; // Reduced padding for a more compact look
            td.style.borderRadius = '4px';
            td.style.color = '#fff'; // White text for better contrast
            dataRow.appendChild(td);
        }

        table.appendChild(dataRow);

        // Create the "Copy" button
        const copyButton = document.createElement('button');
        copyButton.textContent = 'Copy Data';
        copyButton.style.marginTop = '15px';
        copyButton.style.padding = '8px 15px';
        copyButton.style.backgroundColor = '#007bff';
        copyButton.style.color = '#fff';
        copyButton.style.border = 'none';
        copyButton.style.borderRadius = '5px';
        copyButton.style.cursor = 'pointer';
        copyButton.style.fontSize = '12px'; // Smaller font size for the button
        copyButton.addEventListener('click', copyTableData);

        tableContainer.appendChild(copyButton);

        // Append the table to the container
        tableContainer.appendChild(table);

        // Add the table container to the webpage
        document.body.appendChild(tableContainer);
        console.log("Excel-like table created and data added.");
    }

    // Function to extract data from the plain text result
    function extractProteinDataFromText() {
        let proteinData = {};

        // Get the entire text content of the page
        const resultText = document.body.textContent;
        console.log("Result text found on the page.");

        // Use regular expressions to extract the specific data points
        const aminoAcidsMatch = resultText.match(/Number of amino acids:\s*(\d+)/);
        const molecularWeightMatch = resultText.match(/Molecular weight:\s*([\d.]+)/);
        const piMatch = resultText.match(/Theoretical pI:\s*([\d.]+)/);
        const instabilityIndexMatch = resultText.match(/The instability index \(II\) is computed to be\s*([\d.]+)/);
        const aliphaticIndexMatch = resultText.match(/Aliphatic index:\s*([\d.]+)/);
        const gravyMatch = resultText.match(/Grand average of hydropathicity \(GRAVY\):\s*([\d.-]+)/);

        // Save the matched results in the proteinData object
        proteinData["Number of amino acids"] = aminoAcidsMatch ? aminoAcidsMatch[1] : "Nope";
        proteinData["Molecular weight"] = molecularWeightMatch ? molecularWeightMatch[1] : "Nope";
        proteinData["Theoretical pI"] = piMatch ? piMatch[1] : "Nope";
        proteinData["Instability index"] = instabilityIndexMatch ? instabilityIndexMatch[1] : "Nope";
        proteinData["Aliphatic index"] = aliphaticIndexMatch ? aliphaticIndexMatch[1] : "Nope";
        proteinData["GRAVY"] = gravyMatch ? gravyMatch[1] : "Nope";

        console.log("Protein data extracted:", proteinData);

        // Create and display the table with the extracted data
        createExcelTable(proteinData);
    }

    // On the form page (https://web.expasy.org/protparam/), listen for paste event and submit the form automatically
    if (window.location.href.includes("web.expasy.org/protparam/")) {
        const sequenceInput = document.querySelector('textarea');
        const form = document.querySelector('form');

        if (sequenceInput && form) {
            console.log("Form detected on the protparam page.");
            // Listen for paste event in the sequence input
            sequenceInput.addEventListener('paste', function(event) {
                setTimeout(function() {
                    if (sequenceInput.value.trim().length > 0) {
                        // Create a form data object
                        const formData = new FormData(form);

                        // Construct the URL for the new tab with form data as query params
                        const urlParams = new URLSearchParams(formData).toString();
                        const targetURL = `${form.action}?${urlParams}`;

                        // Open the new tab with the constructed URL
                        console.log("Opening results in a new tab...");
                        window.open(targetURL, '_blank');

                        // Reset the form in the original tab
                        console.log("Resetting the form in the original tab...");
                        form.reset();
                    }
                }, 100); // Delay to ensure paste is completed
            });
        }
    }

    // Function to display a temporary notification
    function showCopyNotification() {
        const notification = document.createElement('div');
        notification.textContent = 'Table data copied to clipboard!';
        notification.style.position = 'fixed';
        notification.style.top = '20px'; // Changed from bottom to top
        notification.style.right = '20px';
        notification.style.backgroundColor = '#28a745';
        notification.style.color = '#fff';
        notification.style.padding = '10px 20px';
        notification.style.borderRadius = '5px';
        notification.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
        notification.style.zIndex = '9999';
        document.body.appendChild(notification);

        // Remove notification after 1 second
        setTimeout(() => {
            notification.remove();
        }, 1000);
    }

    // Function to copy the table data to the clipboard
    function copyTableData() {
        const table = document.querySelector('table');

        // Create a temporary textarea to store table data for copying
        const tempTextArea = document.createElement('textarea');
        tempTextArea.style.position = 'fixed'; // Prevent it from appearing in the view
        tempTextArea.style.opacity = '0';
        document.body.appendChild(tempTextArea);

        // Collect data from each table cell, excluding the title row
        let tableData = '';
        table.querySelectorAll('tr').forEach((row, rowIndex) => {
            if (rowIndex > 0) { // Skip the header row (rowIndex 0)
                row.querySelectorAll('td').forEach(cell => {
                    tableData += cell.textContent.trim() + '\t'; // Tab-separated values
                });
                tableData += '\n'; // New line for each row
            }
        });

        // Set the collected table data into the textarea
        tempTextArea.value = tableData;
        tempTextArea.select();
        document.execCommand('copy');
        document.body.removeChild(tempTextArea); // Remove the temporary textarea

        // Show the copy notification
        showCopyNotification();
    }

    // Wait for the page to load and then extract the data
    window.addEventListener('load', extractProteinDataFromText);
})();