// ==UserScript==
// @name B站广告替换为Pixiv推荐图片
// @name:en Bilibili Ad Replacement with Pixiv Recommended Images
// @namespace http://tampermonkey.net/
// @version 1.5.2
// @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", async function(divs) {
let pixivIDs = new Set(); // 去重
divs.forEach(div => {
div.querySelectorAll('a[href*="artworks/"]').forEach(anchor => {
let match = anchor.href.match(/artworks\/(\d+)/);
if (match) {
pixivIDs.add(match[1]);
}
});
});
let uniquePixivIDs = Array.from(pixivIDs);
console.log("推荐页抓取的 Pixiv IDs:", uniquePixivIDs);
if (uniquePixivIDs.length > 0) {
await fetchAndStorePixivUrls(uniquePixivIDs);
GM_setValue("pixivFetched", true);
GM_setValue("isFetchingPixiv", false);
closePage();
}
});
}
}
// === 处理 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')
.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').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();
}
})
}
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'
).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;
}
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('img');
if (img && dataIndex !== null) {
let title = 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 });
})();