Greasy Fork

Greasy Fork is available in English.

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

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

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

您需要先安装一款用户脚本管理器扩展,例如 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.5.3
// @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/*
// @grant          GM_openInTab
// @grant          GM_addValueChangeListener
// @grant          GM_setValue
// @grant          GM_getValue
// @grant          GM_xmlhttpRequest
// @license MIT

// ==/UserScript==
let randomPage = GM_getValue("randomPage", 42);
let RecUrl = `1000users%E5%85%A5%E3%82%8A%20-%E3%82%B3%E3%82%A4%E3%82%AB%E3%83%84!/illustrations?mode=safe&p=${randomPage}&s_mode=s_tag&type=illust&wlt=3000&hlt=3000&ratio=0.5&ai_type=1`;


(function() {
    'use strict';

    let preloadedImages = [];
    const currentUrl = window.location.href;

    function preloadImages(imageUrls) {
        imageUrls.forEach(url => {
            if (!preloadedImages.some(img => img.src === url)) {
                let img = new Image();
                img.src = url;
                preloadedImages.push(img);
            }
        });
    }


// === 处理 Pixiv 页面 ===

    if (currentUrl.includes("www.pixiv.net/") || currentUrl.includes(RecUrl) ){
        console.log("少女祈祷中...");
        function waitForElements(selector, callback, timeout = 10000) {
            console.log("等待" + selector);
            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 fetchAndStorePixivUrls(uniquePixivIDs) {
            let imgUrls = [];
            let additionalData = [];
            let RecimgUrls = GM_getValue("RecimgUrls", []);
            let RecadditionalData = GM_getValue("RecadditionalData", []);

            for (const pixivID of uniquePixivIDs) {
                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 rawImgUrl = data.body.urls.regular.replace(/\\/g, "");
                        let Pminiurl = data.body.urls.mini;
                        const artworkUrl = `https://www.pixiv.net/artworks/${pixivID}`;
                        const username = data.body.userName;


                        const dateMatch = rawImgUrl.match(/\/(\d{4})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d+)/);
                        let thumbUrl = "";
                        let formattedDate = "";
                        let illustTitle = "";
                        let userUrl = "";
                        if (dateMatch) {
                            const [ , year, month, day, hour, minute, second, id] = dateMatch;
                            thumbUrl = `https://i.pixiv.cat/c/360x360_70/img-master/img/${year}/${month}/${day}/${hour}/${minute}/${second}/${id}_p0_square1200.jpg`;
                            formattedDate = `${year}-${month}-${day}`;
                            illustTitle = data.body.title;
                            userUrl = `https://www.pixiv.net/users/${data.body.tags.authorId}`;
                        }

                        let RecimgUrl = rawImgUrl.replace("i.pximg.net/", "i.pixiv.cat/");
                        let miniurl = Pminiurl.replace("i.pximg.net/", "i.pixiv.cat/");

                        if (currentUrl.includes(RecUrl)) {
                            RecimgUrls.push(RecimgUrl);
                            RecadditionalData.push({
                                username,
                                artworkUrl,
                                miniurl
                            });
                        } else {
                            imgUrls.push(thumbUrl || rawImgUrl);
                            additionalData.push({
                                title: illustTitle,
                                artworkUrl,
                                userUrl,
                                date: formattedDate,
                                username,
                            });
                        }
                    }
                } catch (error) {
                    console.error(`获取 pixivID ${pixivID} 信息时报错:`, error);
                }
            }


            if (currentUrl.includes(RecUrl)) {
                GM_setValue("RecimgUrls", RecimgUrls);
                GM_setValue("RecadditionalData", RecadditionalData);
                preloadImages(RecimgUrls);
            } else {
                GM_setValue("pixivImgUrls", imgUrls);
                GM_setValue("pixivAdditionalData", additionalData);
            }
            closePage();
        }

        // 实验功能...
        function getImageMainColorFromPreloaded(img) {
            return new Promise((resolve, reject) => {
                let canvas = document.createElement("canvas");
                let ctx = canvas.getContext("2d");

                img.onload = function () {
                    canvas.width = img.width;
                    canvas.height = img.height;
                    ctx.drawImage(img, 0, 0);

                    let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
                    let r = 0, g = 0, b = 0, count = 0;

                    for (let i = 0; i < imageData.length; i += 4) {
                        r += imageData[i];
                        g += imageData[i + 1];
                        b += imageData[i + 2];
                        count++;
                    }

                    resolve(`rgb(${Math.floor(r / count)}, ${Math.floor(g / count)}, ${Math.floor(b / count)})`);
                };

                img.onerror = () => reject("图片加载失败");
            });
        }



        let Fetching = GM_getValue("isFetchingPixiv", []);
        if (Fetching === false & !currentUrl.includes(RecUrl)) {
            console.log("少女不用祈祷...");
            return;
        }

        console.log(RecUrl);
        if (currentUrl.includes(RecUrl)) {
            console.log("抓取 RecID...");
            GM_setValue("randomPage", Math.floor(Math.random() * 42) + 1);

            waitForElements("div[class*='juyBTC']", async function(divs) {
                let RecID = new Set();
                setTimeout(() => {
                    divs.forEach(div => {
                        div.querySelectorAll('a[href*="artworks/"]').forEach(anchor => {
                            let match = anchor.href.match(/artworks\/(\d+)/);
                            if (match) {
                                RecID.add(match[1]);
                            }
                        });
                    });

                    let existingRecimgUrls = GM_getValue("RecimgUrls", []);
                    let numToFetch = existingRecimgUrls.length === 0 ? 18 : 9; // 为空抓18,否则抓9
                    let uniquePixivIDs = Array.from(RecID)
                    .sort(() => 0.5 - Math.random())
                    .slice(0, numToFetch);
                    console.log(`抓取 ${numToFetch} 个 ID:`, uniquePixivIDs);
                    fetchAndStorePixivUrls(uniquePixivIDs);

                }, 2000);
            });


        } else {
            waitForElements("div.gtm-toppage-thumbnail-illustration-recommend-works-zone", function(divs) {
                divs.forEach(div => {
                    const observer = new MutationObserver((mutations, obs) => {
                        let anchors = div.querySelectorAll('a[href*="artworks/"]');
                        if (anchors.length > 0) {
                            let pixivIDs = new Set();
                            anchors.forEach(anchor => {
                                let match = anchor.href.match(/artworks\/(\d+)/);
                                if (match) {
                                    pixivIDs.add(match[1]);
                                }
                            });
                            console.log("推荐页抓取的 Pixiv IDs:", Array.from(pixivIDs));
                            obs.disconnect();
                            if (pixivIDs.size > 0) {
                                fetchAndStorePixivUrls(Array.from(pixivIDs)).then(() => {
                                    GM_setValue("pixivFetched", true);
                                    GM_setValue("isFetchingPixiv", false);
                                    closePage();
                                });
                            }
                        }
                    });
                    observer.observe(div, { childList: true, subtree: true });
                });
            });


        }
    }


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

    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, .bili-video-card__info--rcmd-text, .bili-video-card__info--owner__up')
                    .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("isFetchingPixiv", true)
                GM_setValue("pixivFetched", false);
                GM_openInTab("https://www.pixiv.net/illustration", { active: false, insert: true, setParent: true });

            }
        }
    }



    function removeSpecificElements() {
        document.querySelectorAll('.floor-single-card, .fixed-card, .v-popover-wrap.left-loc-entry, .palette-button-adcard.is-bottom')
            .forEach(element => {
            element.remove();
        });
        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();
            }
        });
        document.querySelectorAll('img.icon[src="https://i0.hdslb.com/bfs/static/jinkela/long/images/eva.png"]')
            .forEach(element => {
            element.remove();
        });
    }


    function hideBarInShadowRoot(root) {
        if (!root) return;

        const barElement = root.querySelector("#bar");
        if (barElement) {
            barElement.style.cssText = "display: none !important;";
            console.log("Found and hidden #bar in shadow DOM.");
        }

        root.querySelectorAll('*').forEach(node => {
            if (node.shadowRoot) {
                hideBarInShadowRoot(node.shadowRoot);
            }
        });
    }

    function removeADsinVideos() {
        document.querySelectorAll(
            '.video-card-ad-small, .video-page-game-card-small, .activity-m-v1.act-now, .ad-report.ad-floor-exp.left-banner, .ad-report.ad-floor-exp.right-bottom-banner, .v-popover-wrap.left-loc-entry, .slide-ad-exp'
        ).forEach(element => {
            while (element.firstChild) {
                element.removeChild(element.firstChild);
            }
        });

        document.querySelectorAll('*').forEach(node => {
            if (node.shadowRoot) {
                hideBarInShadowRoot(node.shadowRoot);
            }
        });
    }


    function replaceCarouselImages() {
        let RecimgUrls = GM_getValue("RecimgUrls", []);
        let RecadditionalData = GM_getValue("RecadditionalData", []);
        let NumberCheck = 9;

        if (RecimgUrls.length < NumberCheck || RecadditionalData.length < NumberCheck) {
            console.log(`图片或数据不足 ${NumberCheck} 条`);
            return;
        }

        let slides = document.querySelectorAll(".vui_carousel__slides .vui_carousel__slide");
        let usedUrls = new Set(); // 记录使用过的 URL

        slides.forEach(slide => {
            let carouselArea = slide.querySelector(".carousel-area");
            let carouselItem = slide.querySelector("a.carousel-item");

            if (carouselArea) {
                let index = parseInt(carouselArea.getAttribute("data-index"), 10); // 获取 data-index 值
                if (!isNaN(index) && index >= 0 && index < NumberCheck) { // 只处理 0-8
                    let picture = carouselArea.querySelector("picture");
                    if (picture) {
                        let sources = picture.querySelectorAll("source");
                        let img = picture.querySelector("img");
                        let newUrl = RecimgUrls[index];

                        sources.forEach(source => {
                            source.srcset = newUrl;
                        });

                        if (img) {
                            img.src = newUrl;

                            // 让图片显示为16:9,并使用 object-fit: cover 裁剪
                            img.style.width = "100%";
                            img.style.height = "100%";
                            img.style.aspectRatio = "16 / 9";
                            img.style.objectFit = "cover"; // 确保填充并裁剪溢出部分
                        }

                        usedUrls.add(newUrl); // 记录使用过的 URL
                        console.log(`已替换第 ${index} 张图片 -> ${newUrl}`);
                    }

                    // 替换 href
                    if (carouselItem) {
                        let newHref = RecadditionalData[index].artworkUrl;
                        carouselItem.href = newHref;
                        carouselItem.target = "_blank";
                    }
                }
            }
        });

        // 删除已使用的图片
        RecimgUrls = RecimgUrls.filter(url => !usedUrls.has(url));
        GM_setValue("RecimgUrls", RecimgUrls);
        carouselImgLoaded = true;
    }

    function normalization(title) { // 空格
    return title.replace(/\s+/g, ' ').trim();
    }


    async function replaceFooterTitle() {
        let indexToTitleMap = new Map(); //data-index->alt标题
        let carouselAreas = document.querySelectorAll('.carousel-area');

        carouselAreas.forEach(area => {
            let dataIndex = area.getAttribute('data-index');
            let img = area.querySelector('.carousel-inner__img img');
            if (img && dataIndex !== null) {
                let title = normalization(img.getAttribute('alt') || "");
                indexToTitleMap.set(title, parseInt(dataIndex, 10));
            }
        });

        console.log("索引映射:", indexToTitleMap);

        let RecadditionalData = await GM_getValue("RecadditionalData", []);

        if (!Array.isArray(RecadditionalData) || RecadditionalData.length < 9) {
            console.error("RecadditionalData为空或长度不足,刷新可解决:", RecadditionalData);
            return;
        }

        let footerTitle = document.querySelector('.carousel-footer-title span');
        let footerLink = document.querySelector('.carousel-footer-title a');

        if (!footerTitle || !footerLink) {
            console.error("未找到大广告的标题");
            return;
        }

        function updateFooterTitle() {
            let currentText = footerTitle.innerText.trim();
            if (indexToTitleMap.has(currentText)) {
                let matchedIndex = indexToTitleMap.get(currentText);

                if (matchedIndex >= 0 && matchedIndex < RecadditionalData.length) {
                    let newText = RecadditionalData[matchedIndex].username;
                    let newHref = RecadditionalData[matchedIndex].artworkUrl;

                    footerTitle.innerText = newText;
                    footerLink.href = newHref;
                    footerLink.target = "_blank"; // 确保新链接在新标签打开
                }
            }
        }


        updateFooterTitle();
        let observer = new MutationObserver(updateFooterTitle);

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


    console.log("Bilibili运行,检测是否需要抓取图片...");

    // Bilibili首页检测
    if (!currentUrl.match(/^https:\/\/www\.bilibili\.com\/(\?|$)/)) {
        console.log("当前页面不是B站首页");
        if (!currentUrl.match(/^https:\/\/www\.bilibili\.com\/video\/(\?|$)/)) {
            const observer = new MutationObserver(removeADsinVideos);
            observer.observe(document.body, { childList: true, subtree: true });
            console.log("当前页面不是B站首页,停止执行。")}
        return
    }

    let isFetchingPixiv = false;
    let carouselImgLoaded = false;

    let RecDataCeck = GM_getValue("RecadditionalData", []);
    RecDataCeck.splice(0, 9); // 删除前 9 个元素
    GM_setValue("RecadditionalData", RecDataCeck);
    replaceCarouselImages();
    replaceFooterTitle();
    GM_openInTab(`https://www.pixiv.net/tags/`+RecUrl, { active: false, insert: true, setParent: true });

    let pixivImgUrls = GM_getValue("pixivImgUrls", []);
    let minThreshold = 3; // 设定最少剩余图片数,低于这个值就触发抓取

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



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

    removeSpecificElements();
    GM_addValueChangeListener("pixivFetched", (name, oldValue, newValue, remote) => {
        if (newValue === true) {
            console.log("图片已抓取,等待更新广告...");
            isPixivImageLoaded = false;
            isFetchingPixiv = false;
            GM_setValue("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 RecadditionalData = GM_getValue("RecadditionalData", []);
    GM_setValue("RecadditionalData", RecadditionalData); // 更新存储
    let observer = new MutationObserver(() => {
        if (!carouselImgLoaded) {

            replaceCarouselImages();
            replaceFooterTitle();
        }
        removeSpecificElements();
        removeAds();
    });
    observer.observe(document.body, { childList: true, subtree: true });

})();