Greasy Fork

Greasy Fork is available in English.

B站广告替换为Pixiv推荐图片

移除B站首页推荐中的所有推广视频广告,包括小火箭🚀,漫画,纪录片等,以及正统广告。使用Pixiv推荐图片替换广告内容。需要提前登陆过pixiv账号,不需要Cookies或者账号token。

当前为 2025-03-01 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           B站广告替换为Pixiv推荐图片
// @name:en        Bilibili Ad Replacement with Pixiv Recommended Images
// @namespace      http://tampermonkey.net/
// @version        1.4.1
// @description    移除B站首页推荐中的所有推广视频广告,包括小火箭🚀,漫画,纪录片等,以及正统广告。使用Pixiv推荐图片替换广告内容。需要提前登陆过pixiv账号,不需要Cookies或者账号token。
// @description:en Remove promotional video ads from Bilibili's homepage recommendations, including small rocket 🚀 ads and regular ads. Use Pixiv recommended images to replace the ads. A Pixiv account must be logged in beforehand, but no cookies or account tokens are required.
// @author         RecycleBee
// @match          *://www.bilibili.com/*
// @match          *://www.pixiv.net/en/*
// @grant          GM_openInTab
// @grant          GM_addValueChangeListener
// @grant          GM_setValue
// @grant          GM_getValue
// @grant          GM_xmlhttpRequest
// @license MIT

// ==/UserScript==

(function() {
    'use strict';

    const currentUrl = window.location.href;


    // === 处理 Pixiv 页面 ===
    if (currentUrl.includes("pixiv.net/en/")) {
        console.log("少女祈祷中...");

        function waitForElements(selector, callback, timeout = 10000) {
            const startTime = Date.now();
            const checkInterval = 500;

            function check() {
                let elements = document.querySelectorAll(selector);
                if (elements.length > 0) {
                    callback(elements);
                } else if (Date.now() - startTime < timeout) {
                    setTimeout(check, checkInterval);
                } else {
                    console.warn("超时,未找到目标元素");
                }
            }

            check();
        }

        function closePage() {
            window.close();
        }

        async function fetchIllustDetails(pixivID) {
            const apiUrl = `https://www.pixiv.net/ajax/illust/${pixivID}`;

            try {
                const response = await fetch(apiUrl);
                const data = await response.json();

                if (data.body && data.body.urls) {
                    let imgUrl = data.body.urls.original;
                    imgUrl = imgUrl.replace(/\\/g, "");
                    const dateMatch = imgUrl.match(/\/(\d{4})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d+)/);
                    if (dateMatch) {
                        const [_, year, month, day, hour, minute, second, id] = dateMatch;

                        const newImgUrl = `https://i.pixiv.cat/c/360x360_70/img-master/img/${year}/${month}/${day}/${hour}/${minute}/${second}/${id}_p0_square1200.jpg`;
                        const formattedDate = `${year}-${month}-${day}`;

                        const illustTitle = data.body.title;
                        const authorId = data.body.tags.authorId;
                        const userUrl = `https://www.pixiv.net/en/users/${authorId}`;
                        const artworkUrl = `https://www.pixiv.net/en/artworks/${pixivID}`;
                        const username = data.body.userName;
                        console.log(`作品ID ${pixivID} 缩略URL:${newImgUrl}`);
                        return {
                            imgUrl: newImgUrl,
                            illustTitle,
                            artworkUrl,
                            userUrl,
                            date: formattedDate,
                            username,
                        };

                    } else {
                        console.warn(`无法提取${pixivID}`);
                        return null;
                    }
                }
            } catch (error) {
                console.error("获取信息时报错:", error);
                return null;
            }
        }

        async function fetchAllPixivUrls(uniquePixivIDs) {
            let imgUrls = [];
            let additionalData = [];
            for (const pixivID of uniquePixivIDs) {
                const result = await fetchIllustDetails(pixivID);
                if (result) {
                    imgUrls.push(result.imgUrl); // 添加图片URL
                    additionalData.push({
                        title: result.illustTitle,
                        artworkUrl: result.artworkUrl,
                        userUrl: result.userUrl,
                        date: result.date,
                        username: result.username,
                    });
                }
            }
            GM_setValue("pixivImgUrls", imgUrls);
            GM_setValue("pixivAdditionalData", additionalData);
            return { imgUrls, additionalData };
        }


        if (GM_getValue("isFetchingPixiv", false)) {
            console.log("少女不用祈祷...");
            return;
        }

        waitForElements("div.gtm-toppage-thumbnail-illustration-recommend-works-zone", async function(divs) {
            let pixivIDs = new Set(); // 去重
            divs.forEach(div => {
                div.querySelectorAll('a[href^="/en/artworks/"]').forEach(anchor => {
                    let href = anchor.getAttribute("href");
                    let match = href.match(/^\/en\/artworks\/(\d+)/);
                    if (match) {
                        pixivIDs.add(match[1]);
                    }
                });
            });

            let uniquePixivIDs = Array.from(pixivIDs); // 转为数组
            await fetchAllPixivUrls(uniquePixivIDs);
            GM_setValue("pixivFetched", true);
            closePage();
        });
        return;

    }

    // === 处理 Bilibili 页面 ===

    // 预加载图片
    function preloadImages(imageUrls) {
        imageUrls.forEach(url => {
            if (!preloadedImages.some(img => img.src === url)) {
                let img = new Image();
                img.src = url;
                preloadedImages.push(img);
            }
        });
    }



    function removeAds() {
        // 1.处理未替换的广告
        document.querySelectorAll('.bili-video-card.is-rcmd').forEach(card => {
            if (!card.classList.contains('enable-no-interest')) {
                let imageLink = card.querySelector('.bili-video-card__image--link');

                if (imageLink) {
                    // 获取父元素的宽度
                    let parentWidth = card.offsetWidth;
                    let parentHeight = parentWidth * (9 / 16);

                    let placeholder = document.createElement("div");
                    placeholder.style.cssText = `
                    position: relative;
                    width: 100%;
                    height: 0;
                    padding-top: 56.25%;
                    background: #f4f4f4;
                    border-radius: 8px;
                    border: 1px dashed #ccc;
                    margin: auto;
                `;

                    let textContainer = document.createElement("div");
                    textContainer.style.cssText = `
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    transform: translate(-50%, -50%);
                    color: #888;
                    font-size: 14px;
                    font-weight: bold;
                    text-align: center;
                    width: 100%;
                `;
                    textContainer.innerText = "🚫 广告已屏蔽";
                    placeholder.appendChild(textContainer);

                    imageLink.replaceWith(placeholder);

                    // 清空文字内容,但保留元素结构,不知道为什么一定得要有字符在innerText,空格还不行。
                    let placeholderText = "\u200B";
                    let titleElement = card.querySelector('.bili-video-card__info--tit');
                    if (titleElement) {
                        let link = titleElement.querySelector('a');
                        if (link) {
                            link.innerText = placeholderText;
                        }
                    }

                    let authorElement = card.querySelector('.bili-video-card__info--author');
                    if (authorElement) authorElement.innerText = placeholderText;

                    let dateElement = card.querySelector('.bili-video-card__info--date');
                    if (dateElement) dateElement.innerText = placeholderText;

                    let creativeAd = card.querySelector('.vui_icon.bili-video-card__info--creative-ad');
                    if (creativeAd) creativeAd.remove();

                    let adInfo = card.querySelector('.bili-video-card__info--ad');
                    if (adInfo) adInfo.remove();


                    isPixivImageLoaded = false;
                    processAdsOrPlaceholders(placeholder);
                }
            }
        });

        // 处理已经替换成占位符的广告封面
        document.querySelectorAll('div').forEach(placeholder => {
            if (placeholder.innerText === "🚫 广告已屏蔽") {
                processAdsOrPlaceholders(placeholder);
            }
        });
    }


    function processAdsOrPlaceholders(element) {
        let pixivImgUrls = GM_getValue("pixivImgUrls", []);
        let additionalData = GM_getValue("pixivAdditionalData", []);
        if (pixivImgUrls.length>0) {
            let imgUrl = pixivImgUrls.shift();
            let { artworkUrl, title, date, username, userUrl} = additionalData.shift();

            if (imgUrl) {
                preloadImages(pixivImgUrls); // 预加载剩余图片

                let img = document.createElement("img");
                img.src = imgUrl;
                img.alt = "Pixiv 图片";
                img.style.cssText = `
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                object-fit: cover;
                border-radius: 8px;
            `;

                //包裹图片
                let link = document.createElement("a");
                link.href = artworkUrl;
                link.target = "_blank";
                link.style.display = "block";
                link.appendChild(img);

                //图片容器
                let imgContainer = document.createElement("div");
                imgContainer.style.cssText = `
                position: relative;
                width: 100%;
                height: 0;
                padding-top: 56.25%;
                background: #f4f4f4;
                border-radius: 8px;
                overflow: hidden;
            `;

                imgContainer.appendChild(link);
                element.removeAttribute('style');
                element.innerHTML = ""; // 清空原内容
                element.appendChild(imgContainer);

                // 更新其他信息
                let titleContainer = element.closest('.bili-video-card').querySelector('.bili-video-card__info--tit');
                if (titleContainer) {
                    titleContainer.title = title;
                    let titleElement = titleContainer.querySelector('a');
                    if (titleElement) {
                        titleElement.innerText = title;
                        titleElement.href = artworkUrl;
                        titleElement.title = title;
                    }
                }

                let ownerElement = element.closest('.bili-video-card').querySelector('.bili-video-card__info--owner');
                if (ownerElement) ownerElement.href = userUrl;

                let authorElement = element.closest('.bili-video-card').querySelector('.bili-video-card__info--author');
                if (authorElement) {
                    authorElement.innerText = username;
                    authorElement.title = username;
                }

                let dateElement = element.closest('.bili-video-card').querySelector('.bili-video-card__info--date');
                if (dateElement) dateElement.innerText = "· " + date;

                // 删除广告标识
                element.closest('.bili-video-card').querySelectorAll('.vui_icon.bili-video-card__info--creative-ad, .bili-video-card__info--ad')
                    .forEach(el => el.remove());

                // 标记 Pixiv 图片已加载
                isPixivImageLoaded = true;

                // 更新存储
                GM_setValue("pixivImgUrls", pixivImgUrls);
                console.log("ID剩余:"+pixivImgUrls.length);
                GM_setValue("pixivAdditionalData", additionalData);
            }
            if (pixivImgUrls.length <= minThreshold && !isFetchingPixiv) {
                console.log(`图片少于 ${minThreshold} 张(当前 ${pixivImgUrls.length} 张),重新抓取...`);
                isFetchingPixiv = true;
                GM_setValue("pixivFetched", false);
                GM_openInTab("https://www.pixiv.net/en/", { active: false, insert: true, setParent: true });

            }
        }
    }



    function removeSpecificElements() {
        // 1.删除所有纪录片、漫画等
        document.querySelectorAll('div[data-v-128321c6]').forEach(element => {
            element.remove();
        });

        // 2.删除所有瀑布流的广告
        document.querySelectorAll('div[data-v-3581b8d4]').forEach(element => {
            if (!element.closest('.feed-card') && element.classList.contains('bili-video-card') && element.classList.contains('is-rcmd') && element.classList.length === 2) {
                element.remove();
            }
        });
    }

    console.log("Bilibili 运行,检测是否需要抓取 Pixiv 图片...");
    let isFetchingPixiv = false;
    let pixivImgUrls = GM_getValue("pixivImgUrls", []); // 检查是否有 URLs
    let minThreshold = 3; // 设定最少剩余图片数,低于这个值就触发抓取

    if (pixivImgUrls.length <= minThreshold && !isFetchingPixiv) {
        console.log(`图片少于 ${minThreshold} 张,重新抓取...`);
        let isFetchingPixiv = true;
        let tab = GM_openInTab("https://www.pixiv.net/en/", { active: false, insert: true, setParent: true });
    }


    // 让火焰净化一切!!!

    let preloadedImages = [];
    removeSpecificElements();
    GM_addValueChangeListener("pixivFetched", (name, oldValue, newValue, remote) => {
        if (newValue === true) {
            console.log("Pixiv 图片已抓取,等待 DOM 加载后更新广告...");
            isPixivImageLoaded = false;
            isFetchingPixiv = false;

            let observer = new MutationObserver(() => {
                if (document.querySelector('.bili-video-card')) {
                    removeAds();
                    observer.disconnect(); // 只执行一次
                }
            });
            observer.observe(document.body, { childList: true, subtree: true });

            setTimeout(() => {
                if (document.body) {
                    removeAds();
                } else {
                    console.warn("DOM未加载");
                    setTimeout(removeAds, 500);
                }
            }, 500);
        }
    });

    let isPixivImageLoaded = false;
    removeAds();

    // 监听 DOM
    let observer = new MutationObserver(() => {
        removeSpecificElements();
        removeAds();
    });
    observer.observe(document.body, { childList: true, subtree: true });

})();