Greasy Fork镜像 支持简体中文。

Imgur: add vote counts to comment scores in the old design

This script adds vote counts (+ and -) to the visible points on comments. They're normally not viewable on the web site. It also removes the "via Android" and "via iPhone" badges, because I have a tendency to click on them accidentally and don't feel that they add any value.

目前為 2023-12-08 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Imgur: add vote counts to comment scores in the old design
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  This script adds vote counts (+ and -) to the visible points on comments. They're normally not viewable on the web site. It also removes the "via Android" and "via iPhone" badges, because I have a tendency to click on them accidentally and don't feel that they add any value.
// @author       Corrodias
// @match        https://imgur.com/gallery/*
// @match        https://imgur.com/user/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=imgur.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let isOldDesign = (typeof imgur !== 'undefined'); // This object only seems to exist in the old design.
    let isGallery = window.location.pathname.startsWith('/gallery/'); // Otherwise, it's a user page.

    // The comments container is not necessarily loaded before the script runs. Wait for it.
    function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector))
                return resolve(document.querySelector(selector));

            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    observer.disconnect();
                    resolve(document.querySelector(selector));
                }
            });

            observer.observe(document.body, { childList: true, subtree: true });
        });
    }

    let commentsSectionSelector = isOldDesign ? '#captions' : isGallery ? '.CommentsList-comments' : '.ProfileComments';

    waitForElement(commentsSectionSelector).then(commentsSection => {
        const mutationObserver = new MutationObserver(mutations => {
            for (const mutation of mutations) {
                for (const node of mutation.addedNodes) {
                    if (node.nodeType === Node.ELEMENT_NODE)
                        if (isOldDesign)
                            processAddedElementOldDesign(node);
                        else
                            processAddedElementNewDesign(node);
                }
            }
        });

        function processAddedElementOldDesign(node) {
            if (isGallery && node.classList.contains('children')) {
                // Replies were expanded, or navigation to a new post loaded a whole, new set of comments. We can safely update just the new nodes.
                updateComments(node);
            } else if (isGallery && node.classList.contains('comment')) {
                // The user navigated to the "context" or "permalink" of an already displayed comment. Imgur modifies the existing structure rather than replace the nodes.
                updateComments(document.querySelector('#captions')); // The node that was added was a reply, not the top level comment, to replace all of them.
            } else if (!isGallery && node.classList.length === 0 && node.tagName === 'DIV') {
                // Comments on a user's "comments" or "replies" pages just loaded.
                updateComments(node);
            }
        }

        function processAddedElementNewDesign(node) {
            if (isGallery && node.classList.contains('GalleryComment-replies')) {
                // Replies were expanded, or navigation to a new post loaded a whole, new set of comments. We can safely update just the new nodes.
                updateComments(node);
            } else if (isGallery && node.classList.contains('GalleryComment')) {
                // The user navigated to the "context" of an already displayed comment. Imgur replaces the top-level comment node, so just reprocess that.
                updateComments(node);
            } else if (!isGallery && node.classList.contains('ProfileComments-comment')) {
                // Comments on a user's "comments" page just loaded.
                updateComments(node);
            }
        }

        function updateComments(node) {
            hideMobileBadges(node);
            removeOldPointsDetails(node);
            if (isOldDesign)
                addPointsDetailsOldDesign(node);
            else if (isGallery)
                addPointsDetailsNewDesignGallery(node);
            else
                addPointsDetailsNewDesignProfile(node);
        }

        function hideMobileBadges(node) {
            if (!isOldDesign) return; // it's more complicated in the new design
            // Hide the Android/iPhone badges. Do not remove them, or React will break.
            let via = node.querySelectorAll('.via');
            for (const a of via) {
                a.style.display = 'none';
            }
        }

        function removeOldPointsDetails(node) {
            // Remove any existing vote counts so that we can calculate them fresh.
            // This is needed because navigating to the "context" or "permalink" of a comment doesn't necessarily add new nodes but rather can modify existing ones.
            let oldPoints = node.querySelectorAll('.points-detail');
            for (const a of oldPoints) {
                a.remove();
            }
        }

        function addPointsDetailsOldDesign(node) {
            let commentElements = node.querySelectorAll('.caption');
            for (const element of commentElements) {
                // Pull the comment data from the React objects.
                let commentData = getCommentDataOldDesign(element);
                if (commentData === undefined || commentData === null) continue; // no data found

                // Build then add an element with the vote counts.
                let newElement = document.createElement('span');
                newElement.textContent = '(+' + commentData.ups + ', -' + commentData.downs + ')';
                newElement.setAttribute('class', 'comment-meta-spacer points-detail');

                let points = element.firstChild.firstChild.querySelector('.comment-username').nextElementSibling.nextElementSibling;
                if (points.classList.contains('via'))
                    points = points.nextElementSibling;
                points.after(newElement);
            }
        }

        function addPointsDetailsNewDesignGallery(node) {
            let commentElements = node.querySelectorAll('.GalleryComment');
            for (const element of commentElements) {
                // Pull the comment data from the React objects.
                let commentData = getCommentDataNewDesignGallery(element);
                if (commentData === undefined || commentData === null) continue; // no data found

                // Build then add an element with the vote counts.
                let newElement = document.createElement('div');
                newElement.textContent = '(+' + commentData.ups + ', -' + commentData.downs + ')';
                newElement.setAttribute('class', 'points undefined points-detail');

                let downVoteButton = element.querySelector('.vote-btn.down');
                if (downVoteButton != null)
                    downVoteButton.after(newElement);
            }
        }

        function addPointsDetailsNewDesignProfile(node) {
            let commentElements = node.classList.contains('ProfileComments-comment') ? [node] : node.querySelectorAll('.ProfileComments-comment');
            for (const element of commentElements) {
                // Pull the comment data from the React objects.
                let commentData = getCommentDataNewDesignProfile(element.lastElementChild);
                if (commentData === undefined || commentData === null) continue; // no data found

                // Build then add an element with the vote counts.
                let newElement = document.createElement('span');
                newElement.textContent = '(+' + commentData.ups + ', -' + commentData.downs + ')';
                newElement.setAttribute('class', 'Comment-actionbar-item Comment-actionbar-item--disabled Comment-upvotes');

                let points = element.querySelector('.Comment-actionbar').firstElementChild;
                points.after(newElement);
            }
        }

        function getCommentDataOldDesign(node) {
            // The rest of the name past the $ is not always identical.
            for (const propertyKey in node) {
                if (propertyKey.startsWith('__reactInternalInstance$'))
                    return node[propertyKey]._currentElement._owner._instance.props.comment;
            }

            return null;
        }

        function getCommentDataNewDesignGallery(node) {
            // The rest of the name past the $ is not always identical.
            for (const propertyKey in node) {
                if (propertyKey.startsWith('__reactFiber$')) {
                    let data = node[propertyKey].return.memoizedProps.comment;
                    return { ups: data.get('upvote_count'), downs: data.get('downvote_count') };
                }
            }

            return null;
        }

        function getCommentDataNewDesignProfile(node) {
            // The rest of the name past the $ is not always identical.
            for (const propertyKey in node) {
                if (propertyKey.startsWith('__reactProps$')) {
                    let data = node[propertyKey].children.props;
                    return { ups: data.upvotes, downs: data.downvotes };
                }
            }

            return null;
        }

        // Update all currently displayed comments.
        updateComments(commentsSection);

        // Do the same on any comment that is loaded dynamically.
        mutationObserver.observe(commentsSection, { childList: true, subtree: true });
    });
})();

// TODO: navigating to different "tabs" in a user profile, in the new design, doesn't reload the page like it does in the old design, and this breaks. Fix that. It may also be inefficient to listen to *every* mutation until then.
// TODO: what if the script loads before the first comments load, in the new design?

QingJ © 2025

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