Greasy Fork

Greasy Fork is available in English.

Enable Picture-in-picture (PiP) & more for Weverse.io

Enable Picture-in-picture (PiP), Auto translate artists' posts on weverse.io

当前为 2023-11-27 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Enable Picture-in-picture (PiP) & more for Weverse.io
// @namespace   Weverse Enhancements
// @match       *://weverse.io/*/live/*
// @include     *://weverse.io/*/live*
// @include     *://weverse.io*
// @grant       GM_getValue
// @grant       GM_setValue
// @version     4.1
// @author      jho / @jhooo_o
// @run-at      document-end
// @description Enable Picture-in-picture (PiP), Auto translate artists' posts on weverse.io
// @license     MIT
// @icon		https://cdn-v2pstatic.weverse.io/wev_web_fe/assets/1.0.0/icons/logo192.png
// ==/UserScript==

let auto_translate_status = GM_getValue('storage_translator', false);
const pip_btn_icon = `
<span class="pzp-pc-ui-button__tooltip">Toggle Picture-in-picture</span>
<span focusable="false" class="pzp-ui-icon pzp-pc-pip-button__icon pzp-pc-viewmode-button__icon" >
<svg class="pzp-ui-icon__svg" width="36" height="36" viewBox="0 0 36 36">
  <g id="Layer_1" data-name="Layer 1" focusable="false">
    <path fill="currentColor" d="M26.8,11.77c-.13-.24-.32-.44-.57-.57-.24-.13-.49-.2-1.16-.2H10.92c-.67,0-.91,.07-1.16,.2-.24,.13-.44,.32-.57,.57-.13,.24-.2,.49-.2,1.16v9.49c0,.67,.07,.91,.2,1.16,.13,.24,.33,.44,.57,.57,.24,.13,.49,.2,3.21,.2h14.15c-1.39,0-1.14-.07-.9-.2,.24-.13,.44-.32,.57-.57,.13-.24,.2-.49,.2-1.16V12.92c0-.67-.07-.91-.2-1.16Zm-1.8,9.53c0,.55-.45,1-1,1h-6.94c-.55,0-1-.45-1-1v-3.5c0-.55,.45-1,1-1h6.94c.55,0,1,.45,1,1v3.5Z" style="fill-rule: evenodd;"/></g>
</svg>
</span>
`;

const trans_button_active = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 38 38'%3E%3Ccircle fill='%2363D5A9' cx='27.3' cy='9.9' r='4.2'/%3E%3Cpath d='M32.9,28.3l-1.9-4c0,0,0,0,0,0l-3.8-8.1c-1.1,0-2.1-0.3-3-0.8l-3.7,7.9c-0.6-0.1-1.3-0.3-2-0.6c-1-0.4-2-1-2.7-1.5l0,0 c-0.1,0-0.1-0.1-0.2-0.1c1.1-1.1,1.9-2.4,2.5-3.9c0.5-1.3,0.8-2.6,0.9-4H22c-0.3-0.6-0.6-1.2-0.8-1.8h-6v-2c0-0.5-0.4-0.9-0.9-0.9 c-0.5,0-0.9,0.4-0.9,0.9v2h-7c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9h7.9l0,0l0,0h3c-0.1,1.1-0.4,2.2-0.8,3.3 c-0.5,1.3-1.3,2.4-2.2,3.4c-1.1-1.1-1.9-2.4-2.4-3.9c-0.2-0.5-0.7-0.7-1.2-0.6c-0.5,0.2-0.7,0.7-0.6,1.2c0.6,1.7,1.5,3.2,2.8,4.5 c-0.7,0.5-1.5,1-2.3,1.3c-1.3,0.5-2.7,0.8-4.2,0.8c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9c1.7,0,3.3-0.3,4.9-1 c1.1-0.4,2.1-1,3-1.8c0.2,0.1,0.3,0.3,0.5,0.4c0.8,0.6,1.9,1.2,3.1,1.7c0.6,0.3,1.2,0.5,1.9,0.6l-1.6,3.4c-0.2,0.5,0,1,0.4,1.2 s1,0,1.2-0.4l1.6-3.5h8.1l1.6,3.5c0.2,0.5,0.8,0.7,1.2,0.4S33.1,28.8,32.9,28.3z M22.4,23.8l3.2-6.9l3.2,6.9H22.4z'/%3E%3C/svg%3E%0A")`;
const trans_button_not_active = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 38 38'%3E%3Ccircle fill='%23d5639a' cx='27.3' cy='9.9' r='4.2'/%3E%3Cpath d='M32.9,28.3l-1.9-4c0,0,0,0,0,0l-3.8-8.1c-1.1,0-2.1-0.3-3-0.8l-3.7,7.9c-0.6-0.1-1.3-0.3-2-0.6c-1-0.4-2-1-2.7-1.5l0,0 c-0.1,0-0.1-0.1-0.2-0.1c1.1-1.1,1.9-2.4,2.5-3.9c0.5-1.3,0.8-2.6,0.9-4H22c-0.3-0.6-0.6-1.2-0.8-1.8h-6v-2c0-0.5-0.4-0.9-0.9-0.9 c-0.5,0-0.9,0.4-0.9,0.9v2h-7c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9h7.9l0,0l0,0h3c-0.1,1.1-0.4,2.2-0.8,3.3 c-0.5,1.3-1.3,2.4-2.2,3.4c-1.1-1.1-1.9-2.4-2.4-3.9c-0.2-0.5-0.7-0.7-1.2-0.6c-0.5,0.2-0.7,0.7-0.6,1.2c0.6,1.7,1.5,3.2,2.8,4.5 c-0.7,0.5-1.5,1-2.3,1.3c-1.3,0.5-2.7,0.8-4.2,0.8c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9c1.7,0,3.3-0.3,4.9-1 c1.1-0.4,2.1-1,3-1.8c0.2,0.1,0.3,0.3,0.5,0.4c0.8,0.6,1.9,1.2,3.1,1.7c0.6,0.3,1.2,0.5,1.9,0.6l-1.6,3.4c-0.2,0.5,0,1,0.4,1.2 s1,0,1.2-0.4l1.6-3.5h8.1l1.6,3.5c0.2,0.5,0.8,0.7,1.2,0.4S33.1,28.8,32.9,28.3z M22.4,23.8l3.2-6.9l3.2,6.9H22.4z'/%3E%3C/svg%3E%0A")`;

const trans_status_btn_style = document.createElement("style");
trans_status_btn_style.setAttribute('type', 'text/css');
trans_status_btn_style.innerHTML = `
            .HeaderView_translate_status:hover {
                background-color: rgba(0,0,0,.04);
                border-radius: 50%;
                margin: -12px;
                padding: 12px;
            }
            .HeaderView_translate_status:before {
                content: "";
                background-image: var(--trans_background);
                display: block;
                height: 38px;
                width: 38px;
            }
            .HeaderView_translate_status:focus-visible {
                outline: none;
            }
            `;

document.head.appendChild(trans_status_btn_style);

const mutation_config = { childList: true, subtree: true };

function toggle_pip() {

    const video_player_wrapper = document.querySelector(".webplayer-internal-source-wrapper");
    const video_player = document.querySelector(".webplayer-internal-video");
    if (document.pictureInPictureElement) {
        document.exitPictureInPicture()
            .then(() => console.log("██████████ Exited Picture-in-picture mode"))
            .catch(error => console.error("██████████ Error exiting PiP mode:", error));
    } else {
        video_player.requestPictureInPicture()
            .then(() => console.log("██████████ Entered Picture-in-picture mode"))
            .catch(error => console.error("██████████ Error entering PiP mode:", error));
    }
}

function toggle_auto_translate() {
    const translateStatusBtn = document.querySelector(".HeaderView_translate_status");
    if (auto_translate_status) {
        auto_translate_status = false;
        translateStatusBtn.style.setProperty("--trans_background", auto_translate_status ? trans_button_active : trans_button_not_active);
        const status = auto_translate_status ? "Auto translator for artists' posts is enabled" : "Auto translator for artists' posts is disabled";
        translateStatusBtn.setAttribute('title', status);
        GM_setValue('storage_translator', false);
        popup_alert(auto_translate_status, status);
    } else {
        auto_translate_status = true;
        translateStatusBtn.style.setProperty("--trans_background", auto_translate_status ? trans_button_active : trans_button_not_active);
        const status = auto_translate_status ? "Auto translator for artists' posts is enabled" : "Auto translator for artists' posts is disabled";
        translateStatusBtn.setAttribute('title', status);
        GM_setValue('storage_translator', true);
        popup_alert(auto_translate_status, status);
    }
}

function popup_alert(auto_translate_status, status) {
    const popup_elem = document.createElement("div");
    popup_elem.classList.add('translate_popup');
    popup_elem.innerText = status;

    if (auto_translate_status) {
        background = 'linear-gradient(135deg,#e0fdfc,#e3fff5)';
        color = '#08ccca';
    } else {
        background = 'linear-gradient(135deg,#fde0e0,#ffe3e3)';
        color = '#c36666';
    }

    const popup_elem_styles = {
        color: color,
        position: 'fixed',
        top: '10px',
        margin: 'auto',
        border: `2px solid ${color}20`,
        borderRadius: '14px',
        backgroundColor: 'white',
        background: background,
        fontWeight: '900',
        padding: '20px 20px',
        opacity: 0,
        right: 0,
        left: 0,
        width: 'fit-content',
        boxShadow: '0 1px 12px #3e3f4e05, 0 5px 24px #3e3f4e1a',
        zIndex: 3999,
        transition: 'opacity 0.5s ease-in-out'
    };

    Object.assign(popup_elem.style, popup_elem_styles);

    document.body.appendChild(popup_elem);

    void popup_elem.offsetWidth;

    popup_elem.style.opacity = 1;
    setTimeout(() => {
        popup_elem.style.opacity = 0;
        setTimeout(() => {
            document.body.removeChild(popup_elem);
        }, 500);
    }, 2000);
}
function append_elem(changes, observer) {
    // Append Picture-in-picture
    const video_player = document.querySelector(".webplayer-internal-video");
    if (video_player) {
        if (video_player.hasAttribute("disablepictureinpicture")) {
            video_player.removeAttribute("disablepictureinpicture");
            console.log("██████████ Picture-in-picture is re-enabled.");
        }

        const locations = document.querySelectorAll(".pzp-pc__bottom-buttons-right, .pzp-mobile-bottom.pzp-mobile__bottom");
        if (locations.length > 0) {
            const pip_btn_exist = Array.from(locations).some(location => location.querySelector(".pzp-button-pip"));
            if (!pip_btn_exist) {
                const btn = document.createElement("button");
                btn.setAttribute("aria-label", "Toggle Picture-in-picture");
                const btn_class_names = locations[0].classList.contains("pzp-mobile-bottom")
                    ? ["pzp-button", "pzp-setting-button", "pzp-mobile__setting-button", "pzp-button-pip"]
                    : ["pzp-button", "pzp-button-pip", "pzp-pc-viewmode-button", "pzp-pc__viewmode-button", "pzp-pc-ui-button"];
                btn_class_names.forEach(item => btn.classList.add(item));
                btn.innerHTML = pip_btn_icon;
                btn.addEventListener("click", () => toggle_pip());
                locations[0].insertBefore(btn, locations[0].lastChild);
            }
        }
    }
    // Append Translation Button's Toggle

    const nav_bar = document.querySelector('div[class*="HeaderView_action__"]');

    if (nav_bar) {
        if (!nav_bar.querySelector('div[class*="HeaderView_action__"] .HeaderView_translate_status[trans_btn_added]')) {
            const trans_status_btn = document.createElement("button");
            trans_status_btn.classList.add("HeaderView_translate_status");
            trans_status_btn.setAttribute("type", "button");
            trans_status_btn.setAttribute("trans_btn_added", "");
            trans_status_btn.style.setProperty("--trans_background", auto_translate_status ? trans_button_active : trans_button_not_active);
            trans_status_btn.setAttribute('title', auto_translate_status ? "Auto translator for artists' posts is active" : "Auto translator for artists' posts is not active");
            trans_status_btn.addEventListener("click", () => toggle_auto_translate());
            nav_bar.insertBefore(trans_status_btn, nav_bar.children[nav_bar.children.length - 2]);

            if (auto_translate_status) {
                start_auto_translate()
            }

        }
    }

}

function start_auto_translate() {
    console.log("Auto translate is activated.");
    auto_translate_observer = new MutationObserver(auto_translate_f);
    auto_translate_observer.observe(document, mutation_config);
}

function stop_auto_translate() {
    console.log("Auto translate is deactivated.");
    autoTranslateArtist = false;
    GM_setValue('storage_translator', false);
    auto_translate_observer.disconnect();
}

function activateSubtitles(button) {
    button.click();
    setTimeout(() => {
        const vtt_element = document.querySelector('div[class*=MomentViewerControlUiView_translation_result'); // Replace 'yourElementId' with the actual ID of the created element
        if (vtt_element) {
            console.log('New moment sub activated!');
            button.setAttribute("auto_clicked", "");
            button.setAttribute("aria-pressed", "true");
        } else {
            // Retry logic for button click
            console.log('Retrying button click...');
            button.click(); // Retry button click
            setTimeout(activateSubtitles, 1000); // Retry after a short delay
        }
    }, 1000);
}

function auto_translate_f(changes, observer) {
    const current_url = window.location.href;
    const valid_url_pattern = /^https:\/\/weverse\.io\/\w+\/(artist|feed|fanpost|moment).*/;

    //todo - moment page can also have artists' comment. this can mess up the auto translate thing. better separate them normal auto-click and moment auto-click
    //refactor/simplify since adding to many check while debugging create a mess

    if (valid_url_pattern.test(current_url)) {
        const artist_posts = document.querySelectorAll('div[class^="PostHeaderView_header_wrap"]:has(em.BadgeView_badge__sSoG5.BadgeView_-artist__jr7QG) button.TranslationButtonView_translation_button__VSa80, button.TranslationButtonView_translation_button__VSa80.TranslationButtonView_style-artist__VoK6K, button.TranslationButtonView_translation_button__VSa80.TranslationButtonView_style-moment__BuR5U');
        if (artist_posts) {
            const not_auto_clicked_buttons = Array.from(artist_posts).filter(button => button.getAttribute("aria-pressed") === "false");

            not_auto_clicked_buttons.forEach(button => {
                const is_moment = /^https:\/\/weverse\.io\/\w+\/moment.*/;

                if (is_moment.test(current_url) && !button.hasAttribute('auto_clicked')) {
                    console.log('This is a moment page.');
                    const moment_video = document.querySelector('.webplayer-internal-video');
                    const old_moment = document.querySelector('div[class^="OldMomentPostView_moment_photo"]');
                    if (moment_video && !moment_video.paused && !button.hasAttribute('auto_clicked')) {
                        activateSubtitles(button);
                    } else {
                        if (old_moment) {
                            console.log('This is old moment format.');
                            console.log(button.hasAttribute('auto_clicked'))
                            if (button.hasAttribute('auto_clicked')) {
                                return
                            } else {
                                button.click();
                                console.log('Old moment sub clicked')
                                button.setAttribute("auto_clicked", "");
                                button.setAttribute("aria-pressed", "true");
                            }
                        }
                        else {
                            console.log('Video paused.');
                        }
                    }
                } else {
                    console.log('2', not_auto_clicked_buttons)
                    button.click();
                    button.setAttribute("auto_clicked", "");
                    button.setAttribute("aria-pressed", "true");
                }
            });
        }
    }
}


// Observers

const elem_appender_observer = new MutationObserver(append_elem);
elem_appender_observer.observe(document, mutation_config);