Weverse Extra

Enable Picture-in-picture, Livestream notifications, Auto Dismiss Notice

安装此脚本?
作者推荐脚本

您可能也喜欢YouTube Quick Actions

安装此脚本
// ==UserScript==
// @name        Weverse Extra
// @namespace   Weverse Enhancements
// @description Enable Picture-in-picture, Livestream notifications, Auto Dismiss Notice
// @match       *://weverse.io/*
// @include     *://weverse.io/*/live*
// @connect     global.apis.naver.com
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_registerMenuCommand
// @grant       GM_unregisterMenuCommand
// @grant       GM_addStyle
// @grant       GM_notification
// @grant       GM_openInTab
// @grant       GM_xmlhttpRequest
// @grant       GM_getResourceURL
// @antifeature ads
// @require     https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js
// @resource    banner https://cdn-contents.weverseshop.io/public/shop/dc3feec0dfa0f1c3a3fa8d93fc0ca9c0.png
// @version     5.0.1
// @author      jho
// @run-at      document-end
// @license     Unlicense
// @icon        https://cdn-v2pstatic.weverse.io/wev_web_fe/assets/1.0.0/icons/logo192.png
// ==/UserScript==

/* -------------------------------------------------------------------------- */
/*                                Skip Iframes                                */
/* -------------------------------------------------------------------------- */

if (window.top !== window.self)
{
    console.log("[DEBUG] In iframe, skipping script execution.");
    return;
}

/* -------------------------------------------------------------------------- */
/*                                  Variables                                 */
/* -------------------------------------------------------------------------- */

const DateTime = luxon.DateTime;
const css = String.raw;
const style =
    css`
            :root {
                --color-primary: rgba(252, 146, 205, 1);
                --color-secondary: rgba(33, 225, 255, 1) ;
            }
            .fancy {
                -webkit-background-clip: text;
                -webkit-text-fill-color: transparent;
                background-image: linear-gradient(
                    45deg,
                    var(--color-primary) 17%,
                    var(--color-secondary) 100%
                );
                background-size: 400% auto;
                background-position: 0% 50%;
                animation: animate-gradient 12s linear infinite;
            }
    
            @keyframes animate-gradient {
                0% {
                    background-position: 0% 50%;
                }
                50% {
                    background-position: 100% 50%;
                }
                100% {
                    background-position: 0% 50%;
                }
            }
    
        `;

let is_logging_enabled = true;
let auto_remove_landing_promos = GM_getValue("autoRemoveLandingPromos", true);
let auto_dismiss_digital_membership_reminder = GM_getValue("autoDissmissDigitalMembershipReminder", true);
let endorsement_enabled = GM_getValue("toggle5050Endorsement", true);
let cookieAllow = GM_getValue("cookieAllow", true);
const pip_btn_icon = `
                <span class="pzp-pc-ui-button__tooltip pzp-pc-ui-button__tooltip--top">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>
                `;

/* -------------------------------------------------------------------------- */
/*                                  Functions                                 */
/* -------------------------------------------------------------------------- */

GM_addStyle(style);

function log(...args)
{
    if (is_logging_enabled && args && args.length > 0)
    {
        console.log(...args);
    }
}

/* ---------------------------------- Menus --------------------------------- */

const menuCommands = [
    {
        label: () => `Auto Remove Landing Promos: ${auto_remove_landing_promos ? "ON" : "OFF"}`,
        toggle: function toggleAutoRemoveLandingPromos()
        {
            auto_remove_landing_promos = !auto_remove_landing_promos;
            GM_setValue("autoRemoveLandingPromos", auto_remove_landing_promos);
            updateMenuCommands();
            window.location.reload();
        },
        id: undefined,
    },
    {
        label: () => `Auto Dismiss Membership Notice On Live: ${auto_dismiss_digital_membership_reminder ? "ON" : "OFF"}`,
        toggle: function toggleAutoDismissDigitalMembershipReminder()
        {
            auto_dismiss_digital_membership_reminder = !auto_dismiss_digital_membership_reminder;
            GM_setValue("autoDissmissDigitalMembershipReminder", auto_dismiss_digital_membership_reminder);
            updateMenuCommands();
            window.location.reload();
        },
        id: undefined,
    },
    {
        label: () => `Auto Allow Cookie Banner: ${cookieAllow ? "ON" : "OFF"}`,
        toggle: function autoRemoveCookieBanner()
        {
            cookieAllow = !cookieAllow;
            GM_setValue("cookieAllow", cookieAllow);
            updateMenuCommands();
            window.location.reload();
        },
        id: undefined,
    },
    {
        label: () => `💖 5050 Endorsement: ${endorsement_enabled ? "ON" : "OFF"}`,
        toggle: function toggle5050Endorsement()
        {
            endorsement_enabled = !endorsement_enabled;
            GM_setValue("toggle5050Endorsement", endorsement_enabled);
            updateMenuCommands();
            window.location.reload();
        },
        id: undefined,
    },
];

function registerMenuCommands()
{
    for (const command of menuCommands)
    {
        command.id = GM_registerMenuCommand(command.label(), command.toggle);
    }
}

function updateMenuCommands()
{
    for (const command of menuCommands)
    {
        if (command.id)
        {
            GM_unregisterMenuCommand(command.id);
        }
        command.id = GM_registerMenuCommand(command.label(), command.toggle);
    }
}

function toggleAutoRemoveLandingPromos()
{
    auto_remove_landing_promos = !auto_remove_landing_promos;
    GM_setValue("autoRemoveLandingPromos", auto_remove_landing_promos);
    updateMenuCommands();
    window.location.reload();
}

function toggleAutoDissmissDigitalMembershipReminder()
{
    auto_dismiss_digital_membership_reminder = !auto_dismiss_digital_membership_reminder;
    GM_setValue("autoDissmissDigitalMembershipReminder", auto_dismiss_digital_membership_reminder);
    updateMenuCommands();
    window.location.reload();
}

function autoRemoveCookieBanner()
{
    cookieAllow = !cookieAllow;
    GM_setValue("cookieAllow", cookieAllow);
    updateMenuCommands();
    window.location.reload();
}

function toggle5050Endorsement()
{
    endorsement_enabled = !endorsement_enabled;
    GM_setValue("toggle5050Endorsement", endorsement_enabled);
    updateMenuCommands();
    window.location.reload();
}

registerMenuCommands();

/* -------------------------------- Menus End ------------------------------- */

/* --------------------------------- Onload --------------------------------- */

function onLoadCleanUp()
{
    console.log("Removing old notifications.");
    let processedNotifications = GM_getValue('processedNotifications', []);
    console.log("Stored Notification Items:", processedNotifications);

    const now = DateTime.now();

    function isRecent(item)
    {
        const from = DateTime.fromMillis(item.timestamp);
        const hoursDifference = now.diff(from, 'hours').hours;
        console.log("From:", from.toISO());
        console.log("To:", now.toISO());
        console.log("Hours Diff:", hoursDifference);
        console.log("Older than 168 hours?", Math.floor(hoursDifference) > 168);
        console.log("----------------------------------------------------------------------");

        return Math.floor(hoursDifference) <= 168;
        // Keep only items not older than a week as debug info.
        // Most livestream will not be here as any notification older than 2 hours is skipped.
    }

    processedNotifications = processedNotifications.filter(isRecent);

    console.log("Cleaned Notification Items:", processedNotifications);
    GM_setValue('processedNotifications', processedNotifications);
}

onLoadCleanUp();

/* -------------------------------- Onload End ----------------------------- */

/* ------------------------------- Dom Changes ------------------------------ */

function domManipulator(changes, observer)
{
    const videoPlayer = document.querySelector(".webplayer-internal-video");
    const isFirefox = /firefox/i.test(navigator.userAgent);
    const page = document.location.href;
    const togglePictureInPicture = () =>
    {
        if (!videoPlayer) return;

        if (document.pictureInPictureElement)
        {
            document.exitPictureInPicture().catch(log);
        }
        else
        {
            videoPlayer.requestPictureInPicture().catch(log);
        }
    };

    if (videoPlayer)
    {
        if (videoPlayer.hasAttribute("disablepictureinpicture"))
        {
            videoPlayer.removeAttribute("disablepictureinpicture");
            log(" Picture-in-picture is re-enabled.");
        }

        // Firefox does not support requestPictureInPicture(). Removing the attribute is enough. User can use built in pip button.
        // Chromium (at least edge) has also added their own pip button on video player that has no disablepictureinpicture attribute.
        // Thus, the following still works, but it's now redundant and can be removed if you want.
        if (isFirefox) return;

        const locations = document.querySelectorAll(".pzp-pc__bottom-buttons-right, .pzp-mobile-bottom.pzp-mobile__bottom");
        if (locations.length > 0)
        {
            const pipButtonExist = Array.from(locations).some(location => location.querySelector(".pzp-button-pip"));
            if (!pipButtonExist)
            {
                const button = document.createElement("button");
                button.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 => button.classList.add(item));
                button.innerHTML = pip_btn_icon;
                button.addEventListener("click", () => togglePictureInPicture());
                locations[0].insertBefore(button, locations[0].lastChild);
            }
        }
    }

    if (auto_dismiss_digital_membership_reminder)
    {
        const elems = document.querySelectorAll("div#custom_flash_message");
        //Only while watching livestream because of 1 min preview. VOD doesn't count as you need membership to get ai-subtitles.
        const isLive = document.querySelector("span.blind")?.innerText === "Live";
        if (elems && isLive)
        {
            elems.forEach(elem =>
            {
                const elems_to_find = elem.querySelectorAll('div');
                const matchedElements = Array.from(elems_to_find).filter(element =>
                {
                    const matched = /Digital Membership/.test(element.innerText);
                    return matched;
                });

                if (matchedElements.length === 0) return;
                log(matchedElements);

                matchedElements.forEach(matchedElement =>
                {
                    const buttons = matchedElement.parentElement.querySelectorAll('button:has(span.blind)');
                    log(buttons);
                    buttons.forEach(button =>
                    {
                        if (button && button.innerText === "close")
                        {
                            log(button);
                            button.dispatchEvent(new MouseEvent("click", {
                                view: document.defaultView,
                                bubbles: true,
                                cancelable: true
                            }));
                        }
                    });
                });
            });
        }
    }

    if (page === "https://weverse.io/")
    {
        const modalButtons = document.querySelector("button.BaseModalView_bottom_button__XNhOi");
        const cookieButtons = document.querySelector("button.w_button_allow");
        // w_button_allow : allow cookie usage
        // w_button_continue : reject and continue
        {

            if (auto_remove_landing_promos && modalButtons)
            {
                if (modalButtons.innerText === "Don't show again for 3 days")
                {
                    log("👇 Autoclicking landing promo.");
                    log(modalButtons, modalButtons.innerText);
                    queueMicrotask(() =>
                    {
                        modalButtons.dispatchEvent(new MouseEvent("click", {
                            view: document.defaultView,
                            bubbles: true,
                            cancelable: true
                        }));
                        document.body.style.overflow = '';
                    });
                }
            }

            if (cookieAllow && cookieButtons)
            {
                log("👇 Autoclicking cookie notice");
                queueMicrotask(() =>
                {
                    cookieButtons.dispatchEvent(new MouseEvent("click", {
                        view: document.defaultView,
                        bubbles: true,
                        cancelable: true
                    }));
                });
            }
        }
    }

    // -- THIS SCRIPT IS BROUGHT TO YOU BY FIFTY FIFTY SUPPORT GROUP --
    if (endorsement_enabled)
    {
        if (page === "https://weverse.io/")
        {
            const titles = document.querySelectorAll("div.MarqueeView_content__2Qs2H:not(.fancy),span.MarqueeView_content__2Qs2H:not(.fancy)");
            titles.forEach(title =>
            {
                const parentOnlyText = Array.from(title.childNodes)
                    .filter(node => node.nodeType === Node.TEXT_NODE)
                    .map(node => node.textContent.trim())
                    .join('');
                if (parentOnlyText === "FIFTY FIFTY")
                {
                    title.classList.add("fancy");
                }
            });
        }

        if (page.startsWith("https://weverse.io/fiftyfifty/"))
        {
            const titles = document.querySelectorAll("span.HeaderCommunityDropdownWrapperView_name__FZXsx:not(.fancy)");
            titles.forEach(title =>
            {
                const parentOnlyText = Array.from(title.childNodes)
                    .filter(node => node.nodeType === Node.TEXT_NODE)
                    .map(node => node.textContent.trim())
                    .join('');
                if (parentOnlyText === "FIFTY FIFTY")
                {
                    title.classList.add("fancy");
                }
            });
        }
    }

    // -- THIS SCRIPT IS BROUGHT TO YOU BY FIFTY FIFTY SUPPORT GROUP --
}

const mutation_config = { childList: true, subtree: true };
const elem_appender_observer = new MutationObserver(domManipulator);
elem_appender_observer.observe(document, mutation_config);

/* -------------------------------------------------------------------------- */
/*                            Notification Listener                           */
/* -------------------------------------------------------------------------- */

//listen to weverse-fired api calls and only fetch more detail notification api endpoint if needed
//since this is fired by weverse, we are not creating unecessary api calls
const originalOpen = XMLHttpRequest.prototype.open;

function getCookie(name)
{
    const value = `; ${document.cookie}`;
    const parts = value.split(`; ${name}=`);
    if (parts.length === 2) return parts.pop().split(';').shift();
    return null;
}

async function generateWeverseUrl(targetUrl, targetPath, queryParamsData)
{
    const baseUrl = targetUrl;
    const encoder = new TextEncoder();
    const queryParams = queryParamsData;
    const wmsgpad = DateTime.now().ts;

    const apiPath = `${targetPath}${queryParams.toString()}`;
    const truncatedPath = apiPath.substring(0, 255);

    const keyStr = '1b9cb6378d959b45714bec49971ade22e6e24e42';
    const cryptoKey = await crypto.subtle.importKey(
        'raw',
        encoder.encode(keyStr),
        { name: 'HMAC', hash: 'SHA-1' },
        false,
        ['sign']
    );
    
    const dataStr = truncatedPath + wmsgpad.toString();
    const signature = await crypto.subtle.sign(
        'HMAC',
        cryptoKey,
        encoder.encode(dataStr)
    );

    const byteArray = new Uint8Array(signature);
    let binary = '';
    byteArray.forEach(byte => binary += String.fromCharCode(byte));
    const wmd = btoa(binary);

    const finalParams = new URLSearchParams(queryParams);
    finalParams.append('wmsgpad', wmsgpad);
    finalParams.append('wmd', wmd);

    return `${baseUrl}${targetPath}${finalParams.toString()}`;
}

async function fetchNotifications()
{
    // bearer token for api call.
    const accessToken = getCookie('we2_access_token');
    const deviceId = getCookie('we2_device_id');

    if (!accessToken || !deviceId) return;

    const queryParams = new URLSearchParams({
        appId: 'be4d79eb8fc7bd008ee82c8ec4ff6fd4',
        excludeGroup: 'COLLECTION,CO_HOST_LIVE,PARTY',
        language: 'en',
        os: 'WEB',
        platform: 'WEB',
        seen: 'true',
        wpf: 'pc',
    });

    const notificationUrl = await generateWeverseUrl('https://global.apis.naver.com/weverse/wevweb', '/noti/feed/v2.0/activities?', queryParams);

    log(notificationUrl);
    //return;
    GM_xmlhttpRequest({
        method: "GET",
        url: notificationUrl,
        headers: {
            "Authorization": `Bearer ${accessToken}`,
            "User-Agent": navigator.userAgent,
            "Accept": "application/json, text/plain, */*",
            "Accept-Language": "en-GB,en;q=0.5",
            "Referer": "https://weverse.io/",
            "Origin": "https://weverse.io",
            "DNT": "1",
            "WEV-device-Id": deviceId,
            "WEV-open-community": "A"
        },
        onload: function (response)
        {
            if (response.status === 200)
            {
                try
                {
                    //log("API Response:", response);
                    const data = JSON.parse(response.responseText);
                    checkForLivestream(data);
                } catch (e)
                {
                    log("Error parsing response:", e);
                }
            } else
            {
                log(response);
                log(`Failed to fetch data. Status: ${response}`);
            }
        },
        onerror: function (error)
        {
            log("Request failed:", error);
        }
    });
}

async function checkForLivestream(data)
{
    const cleanTitle = (title) => title.replace(/##ARTISTMARK##/g, '').replace(/##(.*?)##/g, '$1');
    const cleanPostId = (id) => id.replace(/:(.*?):/g, '');
    const now = DateTime.now();

    if (!data) return;
    log("Notification Data:", data);
    
    for (const notification of data?.data || [])
    {
        const apiId = notification?.messageId;
        const apiPostId = cleanPostId(apiId);
        const apiCommunityId = notification?.community.communityId;
        const apiActivityType = notification?.activityType; // "ARTIST_LIVE_ON_AIR".
        const apiArtistName = notification?.title;
        const apiNotificationTitle = cleanTitle(notification?.message?.values?.en ?? '');
        const apiNotificationImage = notification?.imageUrl ??
            notification?.logoUrl ??
            '';
        const apiNotificationUrl = "https://weverse.io" + notification?.webUrl;
        const apiTimestamp = notification?.time;

        const apiNotificationObject = {
            id: apiPostId,
            communityId: apiCommunityId,
            type: apiActivityType,
            artistName: apiArtistName,
            title: apiNotificationTitle,
            image: apiNotificationImage,
            url: apiNotificationUrl,
            timestamp: apiTimestamp
        };

        // Check if the notification is a livestream.
        if (apiActivityType === "ARTIST_LIVE_ON_AIR")
        {
            log("Livestream found:", notification);
            let processedNotification = GM_getValue('processedNotifications', []);
            const exist = processedNotification.some((item) => item.id === apiPostId);

            const from = DateTime.fromMillis(apiTimestamp);
            const diffs = now.diff(from, "hours");
            const hoursDiffs = diffs.toObject().hours;

            //log("From: ", from);
            //log("To: ", now);
            //log("Hours Diff: ", hoursDiffs);
            //log("Older than 1 hour ago? ", Math.floor(hoursDiffs) > 1);

            if (Math.floor(hoursDiffs) < 2)
            {
                // Most livestream is less than 1 hour long, and at best less than 2 hours.
                log(`Livestream is less than 2 hour ago [${Math.floor(hoursDiffs)} hour(s)]. Fetching livestream status...`);
                if (!exist)
                {
                    log("Notification is not yet processed: ", exist);
                    log("Checking live status now...");
                    processedNotification.push(apiNotificationObject);
                    GM_setValue('processedNotifications', processedNotification);

                    const accessToken = getCookie('we2_access_token');
                    const deviceId = getCookie('we2_device_id');
                    const queryParamsHasOnAirLivePost = new URLSearchParams({
                        appId: 'be4d79eb8fc7bd008ee82c8ec4ff6fd4',
                        language: 'en',
                        os: 'WEB',
                        platform: 'WEB',
                        wpf: 'pc',
                        fields: 'hasOnAirLivePost'
                    });

                    const queryParamsPostId = new URLSearchParams({
                        appId: 'be4d79eb8fc7bd008ee82c8ec4ff6fd4',
                        language: 'en',
                        os: 'WEB',
                        platform: 'WEB',
                        wpf: 'pc',
                        fieldSet: 'postV1'
                    });

                    //// Checking via post details
                    //const livestreamStatusUrlPostId = await generateWeverseUrl('https://global.apis.naver.com/weverse/wevweb', `/post/v1.0/post-${apiPostId}?`, queryParamsPostId);
                    // data.extension.video.type === "LIVE"
                    // data.extension.video.status === "ONAIR"

                    //// Checking via community tab status
                    const livestreamStatusUrlHasOnAirLivePost = await generateWeverseUrl('https://global.apis.naver.com/weverse/wevweb', `/community/v1.0/community-${apiCommunityId}?`, queryParamsHasOnAirLivePost);

                    GM_xmlhttpRequest({
                        method: "GET",
                        url: livestreamStatusUrlHasOnAirLivePost,
                        headers: {
                            "Authorization": `Bearer ${accessToken}`,
                            "User-Agent": navigator.userAgent,
                            "Accept": "application/json, text/plain, */*",
                            "Accept-Language": "en-GB,en;q=0.5",
                            "Referer": "https://weverse.io/",
                            "Origin": "https://weverse.io",
                            "DNT": "1",
                            "WEV-device-Id": deviceId,
                            "WEV-open-community": "A"
                        },
                        onload: function (response)
                        {
                            // livestreamStatusUrlPostId
                            //     if (response.status === 200)
                            //     {
                            //         try
                            //         {
                            //             const data = JSON.parse(response.responseText);
                            //             if (data?.extension?.video?.type === "LIVE" && data?.extension?.video?.status === "ONAIR")
                            //             {
                            //                 GM_notification({
                            //                     text: apiNotificationTitle,
                            //                     title: apiArtistName,
                            //                     image: apiNotificationImage,
                            //                     onClick: () =>
                            //                     {
                            //                         GM_openInTab(apiNotificationUrl);
                            //                     }
                            //                 });
                            //             }
                            //         } catch (e)
                            //         {
                            //             log("Error parsing response:", e);
                            //         }
                            //     } else
                            //     {
                            //         try
                            //         {
                            //             const data = JSON.parse(response.responseText);
                            //             if (data.errorCode === "digital_membership_710")
                            //             {
                            //                 GM_notification({
                            //                     text: apiNotificationTitle,
                            //                     title: apiArtistName,
                            //                     image: apiNotificationImage,
                            //                     onClick: function ()
                            //                         {
                            //                             GM_openInTab(apiNotificationUrl, { active: true, insert: true });
                            //                         }
                            //                 });
                            //             } else
                            //             {
                            //                 log(response);
                            //                 log(`Failed to fetch data. Status: ${response}`);
                            //             }
                            //         } catch (e)
                            //         {
                            //             log("Error parsing response:", e);
                            //         }
                            //     }
                            //     console.timeEnd('API Request Time'); // End the timer and log the time
                            // },
                            // onerror: function (error)
                            // {
                            //     log("Request failed:", error);
                            //     console.timeEnd('API Request Time'); // End the timer in case of error
                            // }
                            // livestreamStatusUrlPostId End
                            if (response.status === 200)
                            {
                                log(response);
                                try
                                {
                                    const data = JSON.parse(response.responseText);
                                    if (data?.hasOnAirLivePost === true)
                                    {
                                        GM_notification({
                                            text: apiNotificationTitle,
                                            title: apiArtistName,
                                            image: apiNotificationImage,
                                            onclick: function ()
                                            {
                                                log("Opening Livestream...", apiNotificationUrl);
                                                GM_openInTab(apiNotificationUrl, { active: true, insert: true });
                                            }
                                        });
                                    }
                                } catch (e)
                                {
                                    log("Error parsing response:", e);
                                }
                            } else
                            {
                                log(`Failed to fetch data. Status: ${response}`);
                            }
                        },
                        onerror: function (error)
                        {
                            log("Request failed:", error);
                        }
                    });
                } else
                {
                    log(`Livestream has been processed. Skipping status check...`);
                    log("----------------------------------------------------------------------");
                }
            } else
            {
                log(`Livestream is more than 2 hour ago [${Math.floor(hoursDiffs)} hour(s)]. Skipping...`);
                log("----------------------------------------------------------------------");
            }
        }
    }
}

XMLHttpRequest.prototype.open = function (method, url, ...rest)
{
    const shouldIntercept = url.includes('https://global.apis.naver.com/weverse/wevweb/noti/feed/v2.0/activities/community',);
    const shouldIntercept2 = url.includes('https://global.apis.naver.com/weverse/wevweb/home/v1.0/home/pc',);
    if (shouldIntercept)
    {
        const originalOnLoad = this.onload;
        this.addEventListener('load', function ()
        {
            try
            {
                const toCompare = GM_getValue('lastNotiV2ActivitiesCommunity', undefined);
                const timestamp = DateTime.now().toLocaleString(DateTime.DATETIME_MED_WITH_SECONDS);
                const data = JSON.parse(this.responseText);

                //data.data?.[0];
                const newData = data.data;
                if (JSON.stringify(newData) === JSON.stringify(toCompare))
                {
                    //data is the same
                    console.log(`⚠️ No new Notification(s) available - ${timestamp}`);
                    return;
                } else
                {
                    console.log(`🚨 New Notification(s) available - ${timestamp}`);
                    GM_setValue('lastNotiV2ActivitiesCommunity', newData);
                    fetchNotifications();
                }

            } catch (e)
            {
                log('🚨 Failed to parse response:', e);
            }

            if (originalOnLoad) originalOnLoad.apply(this, arguments);
        }, { once: true }); // Listen once
    }

    const now = DateTime.now();
    const expiryDate = DateTime.fromISO('2025-06-01');
    let bannerImageUrl = GM_getResourceURL("banner") || "https://i.postimg.cc/L84gDf2X/dc3feec0dfa0f1c3a3fa8d93fc0ca9c0.png";
    if (shouldIntercept2 && endorsement_enabled && (now < expiryDate))
    {
        const originalOnLoad = this.onload;
        this.addEventListener('load', function ()
        {
            try
            {
                let data = JSON.parse(this.responseText);

                const toPush = {
                    "bannerId": 5050,
                    "startDate": 0,
                    "endDate": 97195420800000,
                    "contentType": "IMAGE_TITLE_SUBTITLE",
                    "content": {
                        "imageUrl": bannerImageUrl,
                        "firstTitle": "3rd Mini Album",
                        "secondTitle": "Day & Night",
                        "subTitle": "I’m your pookie in the morning,",
                        "secondSubTitle": "I'm your pookie in the night! 💖",
                        "textColor": "#000000"
                    },
                    "landingUrl": "https://weverse.io/fiftyfifty/feed",
                    "landingUrlType": "EXTERNAL_WEBLINK",
                    "communityName": "FIFTY FIFTY"
                };

                data.mainBanners.splice(1, 0, toPush);

                const modifiedResponseText = JSON.stringify(data);

                Object.defineProperty(this, 'responseText', {
                    value: modifiedResponseText,
                    writable: true,
                    configurable: true
                });

                this.responseXML = new DOMParser().parseFromString('<root></root>', 'application/xml');
            } catch (e)
            {
                console.error('🚨 Failed to parse response:', e);
            }

            if (originalOnLoad) originalOnLoad.apply(this, arguments);
        }, { once: true });
    }

    return originalOpen.apply(this, [method, url, ...rest]);
};

QingJ © 2025

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