Torn Chat Timestamp

Displays the time a chat message was sent right by it.

当前为 2021-08-24 提交的版本,查看 最新版本

// ==UserScript==
// @name         Torn Chat Timestamp
// @namespace    https://www.torn.com
// @version      1.2.3
// @description  Displays the time a chat message was sent right by it.
// @author       PhilMe [2590086]
// @match        *.torn.com/*
// @grant        none
/*jshint esversion: 6 */
// ==/UserScript==


(function() {
    'use strict';
    let timestampColor = {red: 169, green: 169, blue: 169};

    let allClasses = [];
    let allElements = document.querySelectorAll('*');
    let chatBoxClass;
    let overviewClass;
    let chatBoxContentClass;
    let messageClass;
    let lastMessageLabelClass;

    /*
    Since class names end with a few random letters and numbers (ex. chat-box-content_3wOdX), and those can change over time,
    this iterates over all class names on the page and finds the full class name for them.
    */
    for (let i = 0; i < allElements.length; i++) {
        let classes = allElements[i].className.toString().split(/\s+/);
        for (let j = 0; j < classes.length; j++) {
            let cls = classes[j];
            if (cls.startsWith("chat-box_") && !chatBoxClass) {
                chatBoxClass = cls;
            }
            else if (cls.startsWith("overview_") && !overviewClass) {
                overviewClass = cls;
            }
            else if (cls.startsWith("chat-box-content_") && !chatBoxContentClass) {
                chatBoxContentClass = cls;
            }
            else if (cls.startsWith("message_") && !messageClass) {
                messageClass = cls;
            }
            else if (cls.startsWith("chat-last-message-label_") && !lastMessageLabelClass) {
                lastMessageLabelClass = cls;
            }
        if (chatBoxClass && overviewClass && chatBoxContentClass && messageClass && lastMessageLabelClass) {break;} // leave loop early if these are all found
        }
    }

    function parseDate (datestring) {
        let re = new RegExp('^([0-2][0-9]):([0-5][0-9]):([0-5][0-9]) - ([0-3][0-9])\/([0-1][0-9])\/([0-9]{2})', 'g');
        let data = re.exec(datestring);
        return `${data[1]}:${data[2]}:${data[3]}`;
    }

    function formatDate(date) {
        let hour = date.getHours().toString().padStart(2, "0");
        let minute = date.getMinutes().toString().padStart(2, "0");
        let second = date.getSeconds().toString().padStart(2, "0");
        return `${hour}:${minute}:${second}`;
    }

    function addTimestamp (element) {
        if (element.getAttribute("class").trim() !== messageClass) {return;} // skip non-messages

        let span = element.querySelector('span');
        let timeString = span.getAttribute("title");

        let timeSpan = document.createElement('span');
        timeSpan.innerHTML = parseDate(timeString) + " ";
        timeSpan.style.color = `rgb(${timestampColor.red}, ${timestampColor.green}, ${timestampColor.blue})`;

        let a = element.querySelector('a');
        element.prepend(timeSpan, a);
    }

    function addChatMessageObserver(element) {
        const messageDiv = element.querySelector(`.${overviewClass}`);

        if (messageDiv !== null) {
            if (!messageClass) {
                messageDiv.childNodes.forEach(node => {
                    if (messageClass) {return;}
                    if (node.className.startsWith("message_")) {
                        messageClass = node.className.trim();
                        return;
                    }
                });
            }
            messageDiv.querySelectorAll(`.${messageClass}`).forEach(msg => {
                addTimestamp(msg);
            });
            chatMessageObserver.observe(messageDiv, {childList: true});
            
            const lastMessageLabel = element.querySelector(`.${lastMessageLabelClass}`);
            if (lastMessageLabel !== null) {
                lastMessageLabel.scrollIntoView();
            } else {
                console.error(`addChatMessageObserver: class '${lastMessageLabelClass}' was not found.`);
            }
        }
    }

    // Watches for if any new chats are created
    let chatObserver = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            for (let i = 0; i < mutation.addedNodes.length; i++) {
                chatOpenedObserver.observe(mutation.addedNodes[i], {childList: true});
            }
        })
    });

    // When a chat is opened, timestamps are added
    let chatOpenedObserver = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            for (let i = 0; i < mutation.addedNodes.length; i++) {
                let element = mutation.addedNodes[i];
                if (element.getAttribute("class").startsWith(chatBoxContentClass)) {
                    addChatMessageObserver(element);
                }
            }
        })
    });

    // When a new message is sent, add timestamp
    let chatMessageObserver = new MutationObserver(function(mutations) {
        mutations.forEach(function(mutation) {
            for (let i = 0; i < mutation.addedNodes.length; i++) {
                addTimestamp(mutation.addedNodes[i]);

            }
        })
    });

    let chatRoot = document.querySelector('#chatRoot').firstElementChild;

    Array.from(chatRoot.getElementsByClassName(chatBoxClass)).forEach(element => {
        addChatMessageObserver(element);
        chatOpenedObserver.observe(element, {childList: true});
    });
    chatObserver.observe(chatRoot, {childList: true}); // watches for new chats

})();