Better Sturdy

Easy image filtering and post marking

目前為 2025-04-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Better Sturdy
// @version      1.0
// @description  Easy image filtering and post marking
// @author       ILoveS10
// @license      MIT
// @match        https://sturdychan.help/*
// @grant        none
// @namespace https://greasyfork.org/users/1461417
// ==/UserScript==

(function () {
    'use strict';

    function hashFiltering(postElement) {
        const image = postElement.querySelector('figure img');
        const filterArea = document.querySelector('#filterArea');

        if (!image || !filterArea) return null;

        const imageURL = image.src;
        const filenameWithExt = imageURL.split('/').pop();
        const filenameWithoutExt = filenameWithExt.split('.')[0];

        const hashLine = `hash:${filenameWithoutExt}`;
        filterArea.value += `\n${hashLine}`;
        return hashLine;
    }

    function showMessage(text, undoCallback) {
        const message = document.createElement('div');
        message.id = 'sturdy-message';
        message.style.position = 'fixed';
        message.style.top = '30px';
        message.style.left = '50%';
        message.style.transform = 'translateX(-50%)';
        message.style.backgroundColor = '#000';
        message.style.color = '#fff';
        message.style.padding = '10px 20px';
        message.style.borderRadius = '8px';
        message.style.zIndex = '10000';
        message.style.fontSize = '16px';
        message.style.boxShadow = '0 2px 10px rgba(0,0,0,0.5)';
        message.style.display = 'flex';
        message.style.alignItems = 'center';
        message.style.gap = '10px';

        const textNode = document.createElement('span');
        textNode.textContent = text;

        const showButton = document.createElement('button');
        showButton.textContent = '[Show]';
        showButton.style.background = 'none';
        showButton.style.color = '#0af';
        showButton.style.border = 'none';
        showButton.style.cursor = 'pointer';
        showButton.style.fontSize = '16px';

        const undoButton = document.createElement('button');
        undoButton.textContent = '[Undo]';
        undoButton.style.background = 'none';
        undoButton.style.color = '#0af';
        undoButton.style.border = 'none';
        undoButton.style.cursor = 'pointer';
        undoButton.style.fontSize = '16px';

        showButton.addEventListener('click', () => {
            const filterBox = document.getElementById('megukascript-options');
            const filterTabs = filterBox?.querySelectorAll('.tab-link');
            const filtersTab = Array.from(filterTabs || []).find(tab => tab.dataset.id === '1');
            const filterArea = document.getElementById('filterArea');

            if (filterBox) {
                filterBox.style.display = 'block';
            }

            if (filtersTab) {
                filtersTab.click();
            }

            if (filterArea) {
                filterArea.scrollIntoView({ behavior: 'smooth', block: 'center' });
            }
        });

        undoButton.addEventListener('click', () => {
            undoCallback();
            message.remove();
        });

        message.appendChild(textNode);
        message.appendChild(showButton);
        message.appendChild(undoButton);

        document.body.appendChild(message);

        const removeMessage = () => {
            message.remove();
            document.removeEventListener('keydown', onKeyDown);
        };

        const onKeyDown = (e) => {
            if (e.key === 'Escape') {
                removeMessage();
            }
        };

        document.addEventListener('keydown', onKeyDown);

        setTimeout(removeMessage, 10000);
    }

    function addButtons() {
        const dropdowns = document.querySelectorAll('ul.popup-menu.glass');

        dropdowns.forEach(dropdown => {
            if (dropdown.querySelector('li[data-id="addHashToFilter"]')) return;

            const post = dropdown.closest('article');
            if (!post) return;

            const hasImage = post.querySelector('figcaption a[download]');
            if (!hasImage) return;

            const hashFilter = document.createElement('li');
            hashFilter.setAttribute('data-id', 'addHashToFilter');
            hashFilter.textContent = 'Filter Hash';

            hashFilter.addEventListener('click', e => {
                e.stopPropagation();
                const hashLine = hashFiltering(post);
                if (!hashLine) return;

                const saveButton = document.getElementById('filterArea_button');
                if (saveButton) {
                    saveButton.click();
                }

                const popupMenu = hashFilter.closest('ul.popup-menu.glass');
                if (popupMenu) {
                    popupMenu.style.display = 'none';
                }

                showMessage('Image Filtered. Refresh page.', () => {
                    const filterArea = document.querySelector('#filterArea');
                    if (!filterArea) return;

                    const lines = filterArea.value.split('\n').filter(line => line.trim() !== hashLine);
                    filterArea.value = lines.join('\n');

                    if (saveButton) {
                        saveButton.click();
                    }
                });
            });

            dropdown.appendChild(hashFilter);
        });
    }

    addButtons();

    const observer = new MutationObserver(addButtons);
    observer.observe(document.body, { childList: true, subtree: true });

})();

function addToggleMarkButtons() {
    document.querySelectorAll('article.glass .popup-menu.glass').forEach(menu => {
        let article = menu.closest('article');
        if (!article) return;

        let existingToggle = menu.querySelector('li[data-id="toggle-own"]');
        if (!existingToggle) {
            const toggleItem = document.createElement('li');
            toggleItem.setAttribute('data-id', 'toggle-own');

            function updateToggleText() {
                toggleItem.textContent = article.classList.contains('postMine') ?
                    'Unmark post as your own' : 'Mark post as your own';
            }

            toggleItem.addEventListener('click', function () {
                const postId = article.id.replace('p', '');
                const replySelectors = `a.post-link[data-id="${postId}"]`;

                if (article.classList.contains('postMine')) {
                    article.classList.remove('postMine');
                    const author = article.querySelector('b.name i');
                    if (author && author.textContent.trim() === '(You)') {
                        author.remove();
                    }

                    document.querySelectorAll(replySelectors).forEach(link => {
                        const replyArticle = link.closest('article');
                        if (replyArticle) {
                            link.innerHTML = link.innerHTML.replace(' (You)', '');
                            replyArticle.classList.remove('postReply');
                        }
                    });
                } else {
                    article.classList.add('postMine');

                    const nameSpan = article.querySelector('b.name span');
                    if (nameSpan && !article.querySelector('b.name i')) {
                        const youMarker = document.createElement('i');
                        youMarker.textContent = ' (You)';
                        nameSpan.insertAdjacentElement('afterend', youMarker);
                    }

                    document.querySelectorAll(replySelectors).forEach(link => {
                        const replyArticle = link.closest('article');
                        if (replyArticle && !link.innerHTML.includes(' (You)')) {
                            link.innerHTML = link.innerHTML.replace(/(>>\d+)/, '$1 (You)');
                            replyArticle.classList.add('postReply');
                        }
                    });
                }
                updateToggleText();
            });

            updateToggleText();
            menu.appendChild(toggleItem);
        }
    });
}

addToggleMarkButtons();
const toggleObserver = new MutationObserver(addToggleMarkButtons);
toggleObserver.observe(document.body, { childList: true, subtree: true });