优化 BoyFriendTV 浏览体验:去除导航栏广告、直播卡片、底栏与边栏冗余区块。
// ==UserScript==
// @name BetterBoyFriendTV
// @namespace BetterGayPornSites
// @version 0.1.5
// @author AtmoOmen
// @description 优化 BoyFriendTV 浏览体验:去除导航栏广告、直播卡片、底栏与边栏冗余区块。
// @match https://www.boyfriendtv.com/*
// @match https://boyfriendtv.com/*
// @run-at document-start
// @license MIT
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// ==/UserScript==
(function () {
"use strict";
const FEATURES = {
nav: { key: "betterBftvNav", label: "精简导航栏" },
topbar: { key: "betterBftvTopbar", label: "精简顶栏" },
live: { key: "betterBftvLive", label: "精简视频流" },
footer: { key: "betterBftvFooter", label: "精简底栏" },
sidebar: { key: "betterBftvSidebar", label: "精简边栏" },
float: { key: "betterBftvFloat", label: "精简浮动组件" },
player: { key: "betterBftvPlayer", label: "精简视频播放" },
};
const state = {};
const commandIds = {};
let bannerPatched = false;
let menuRetries = 0;
const loadValue = (key, fallback) => {
try {
return GM_getValue(key, fallback);
} catch (e) {
return fallback;
}
};
for (const name in FEATURES) {
state[name] = loadValue(FEATURES[name].key, true);
}
const normalize = (value) => (value || "").replace(/\s+/g, " ").trim();
function cleanNav() {
for (const item of document.querySelectorAll("ul.navmenu > li")) {
const link = item.querySelector(":scope > a");
if (link && ["Live Sex", "AI Lover", "Creators"].includes(normalize(link.textContent))) {
item.remove();
}
}
}
function cleanLive() {
for (const tile of document.querySelectorAll("li.mtile-x7")) {
const isLive =
tile.querySelector('a[href*="/link/out/cam"]') !== null ||
[...tile.querySelectorAll(".js-rating-text-percent")].some((el) => normalize(el.textContent) === "LIVE");
if (isLive) {
tile.remove();
}
}
for (const tile of document.querySelectorAll("div.content-item.video-item-wrapper")) {
const isLive =
tile.querySelector('a[href*="/link/out/cam"]') !== null ||
[...tile.querySelectorAll(".content-rating")].some((el) => normalize(el.textContent) === "LIVE");
if (isLive) {
tile.remove();
}
}
if (document.body && document.body.classList.contains("js-mobile")) {
for (const frame of document.querySelectorAll('iframe[src*="janusaent.com/widgets"], iframe[src*="mob-foot.html"]')) {
const box = frame.closest(".box-listing");
if (box) {
box.remove();
} else {
const block = frame.closest(".content-block");
if (block) {
block.remove();
}
}
}
}
for (const heading of document.querySelectorAll("h2.amateur-promo-heading")) {
if (normalize(heading.textContent).toLowerCase() === "best creator videos") {
const list = heading.nextElementSibling;
heading.remove();
if (list && list.classList.contains("ast-model-promo-list")) {
list.remove();
}
}
}
}
function cleanTopbar() {
const labels = ["Follow us @ Twitter", "Shemale Porn", "Fetish Porn", "Premium Male Access"];
for (const item of document.querySelectorAll("ul.pull-left.topmenu > li")) {
const link = item.querySelector(":scope > a");
if (link && labels.includes(normalize(link.textContent))) {
item.remove();
}
}
const mobileLabels = ["AI Boys", "Pornstars", "Creators", "Porn AI"];
for (const item of document.querySelectorAll("ul.top-links-menu > li")) {
const link = item.querySelector(":scope > a");
if (link && mobileLabels.includes(normalize(link.textContent))) {
item.remove();
}
}
}
function cleanFloat() {
for (const el of document.querySelectorAll("#js-chat-bubble, .chat-bubble, #js-chat-panel")) {
el.remove();
}
}
function patchBannerManager() {
if (bannerPatched) {
return;
}
let manager = window.CookieManager;
Object.defineProperty(window, "CookieManager", {
configurable: true,
get() {
return manager;
},
set(value) {
manager = value;
if (value && typeof value.getBannerDisplayed === "function") {
const originalGetBanner = value.getBannerDisplayed;
value.getBannerDisplayed = function (key) {
if (key === "schat") {
return true;
}
return originalGetBanner.call(this, key);
};
}
},
});
window.CookieManager = manager;
bannerPatched = true;
}
function cleanPlayer() {
patchBannerManager();
const isPlayerArea = (el) =>
el && (el.id === "mediaplayer_wrapper" || el.id === "video-details" || el.classList.contains("video-detail") ||
el.querySelector("#mediaplayer_wrapper, #video-details, .video-detail") !== null);
for (const overlay of document.querySelectorAll(".az6-overlay")) {
overlay.remove();
}
if (document.body) {
document.body.classList.remove("az6-overlay-shown");
}
if (!location.pathname.startsWith("/videos/")) {
return;
}
for (const ad of document.querySelectorAll("a.ads-stripchat-link")) {
const item = ad.parentElement && ad.parentElement.tagName === "LI" ? ad.parentElement : ad.closest("li");
if (item && !isPlayerArea(item)) {
item.remove();
} else if (!isPlayerArea(ad)) {
ad.remove();
}
}
const rightside = document.querySelector(".rightside2");
if (rightside && !isPlayerArea(rightside)) {
rightside.remove();
const main = document.querySelector("#maincolumn2");
if (main) {
main.classList.remove("has-right-ads");
}
}
for (const block of document.querySelectorAll(".info-block-wrapper")) {
if (!isPlayerArea(block)) {
block.remove();
}
}
for (const wrapper of document.querySelectorAll(".zn-wrapper")) {
if (!isPlayerArea(wrapper)) {
wrapper.remove();
}
}
for (const script of document.querySelectorAll("script")) {
if (/fh-wgt\.com|FapHouse/i.test(script.textContent)) {
const host = script.previousElementSibling;
if (host && host.tagName === "DIV" && !isPlayerArea(host)) {
host.remove();
}
script.remove();
}
}
for (const label of document.querySelectorAll(".zn-label")) {
const host = label.closest("div");
if (host && !isPlayerArea(host)) {
host.remove();
}
}
for (const ad of document.querySelectorAll("a.q1-line, a.below-video-link")) {
const host = ad.parentElement;
if (host && host.tagName === "DIV" && !isPlayerArea(host)) {
host.remove();
} else if (!isPlayerArea(ad)) {
ad.remove();
}
}
}
function cleanFooter() {
const headings = [
"Videos of BoyFriendTV Creators",
"Last commented models",
"Chat with LIVE models",
"Last commented videos",
];
for (const wrapper of document.querySelectorAll(".js-toggle-content-wrapper")) {
const menu = wrapper.querySelector(".js-toggle-content-menu");
if (menu && headings.some((heading) => normalize(menu.textContent).startsWith(heading))) {
wrapper.remove();
}
}
for (const wrapper of document.querySelectorAll(".js-toggle-content-wrapper.block-amateur")) {
wrapper.remove();
}
for (const heading of document.querySelectorAll("#main-v6 > div.vtab")) {
const block = heading.nextElementSibling;
if (block && block.classList.contains("content-block") && block.classList.contains("default-padding")) {
heading.remove();
block.remove();
}
}
for (const heading of document.querySelectorAll("h3")) {
if (normalize(heading.textContent).startsWith("Chat with LIVE models")) {
const container = heading.parentElement;
if (container && container.classList.contains("container")) {
container.remove();
}
}
}
}
function cleanSidebar() {
for (const title of document.querySelectorAll(".left-menu-box-title")) {
const text = normalize(title.textContent);
if (text === "Recommended") {
const block = title.closest(".left-menu-box");
if (block) {
block.remove();
}
} else if (text === "Top Creators" || text === "Top Pornstars") {
const sibling = title.nextElementSibling;
title.remove();
if (sibling && (sibling.tagName === "DIV" || sibling.tagName === "UL")) {
sibling.remove();
}
}
}
const tagsBlock = [...document.querySelectorAll(".left-menu-box")].find(
(box) => box.querySelector(":scope > .left-menu-box-title") &&
normalize(box.querySelector(":scope > .left-menu-box-title").textContent) === "Tags"
);
if (tagsBlock) {
const homeList = [...tagsBlock.querySelectorAll("ul.left-menu-box")].find((list) =>
[...list.querySelectorAll("a")].some((link) => normalize(link.textContent) === "Home")
);
if (homeList) {
for (const item of [...homeList.children]) {
const link = item.querySelector(":scope > a");
if (!link) {
continue;
}
const text = normalize(link.textContent);
if (link.querySelector('img[alt="Country flag"]') || text === "Creators" || text === "Live Cam Models") {
item.remove();
}
}
}
}
const mobileLabels = ["Creators' Videos", "Pornstars", "Creators", "Live Cam Models", "Live Sex"];
for (const item of document.querySelectorAll("div.navmenu.navbar-default > ul.navmenu-nav > li")) {
const link = item.querySelector(":scope > a");
if (link && mobileLabels.includes(normalize(link.textContent))) {
item.remove();
}
}
if (document.body && document.body.classList.contains("js-mobile")) {
for (const item of document.querySelectorAll(".trending-tags > li")) {
const link = item.querySelector(":scope > a");
if (link && link.querySelector('img[alt="Country flag"]')) {
item.remove();
}
}
}
}
function clean() {
if (state.nav) {
cleanNav();
}
if (state.topbar) {
cleanTopbar();
}
if (state.live) {
cleanLive();
}
if (state.footer) {
cleanFooter();
}
if (state.sidebar) {
cleanSidebar();
}
if (state.float) {
cleanFloat();
}
if (state.player) {
cleanPlayer();
}
}
function registerMenus() {
try {
for (const name in FEATURES) {
const feature = FEATURES[name];
if (commandIds[name] != null) {
GM_unregisterMenuCommand(commandIds[name]);
}
commandIds[name] = GM_registerMenuCommand(`${feature.label}:${state[name] ? "开" : "关"}`, () => {
state[name] = !state[name];
GM_setValue(feature.key, state[name]);
registerMenus();
clean();
});
}
} catch (e) {
menuRetries += 1;
if (menuRetries < 20) {
setTimeout(registerMenus, 500);
}
}
}
let scheduled = false;
const observer = new MutationObserver(() => {
if (scheduled) {
return;
}
scheduled = true;
setTimeout(() => {
scheduled = false;
clean();
}, 0);
});
function start() {
registerMenus();
clean();
observer.observe(document.documentElement, { childList: true, subtree: true });
document.addEventListener(
"click",
(e) => {
if (!state.player) {
return;
}
const target = e.target;
if (!target || typeof target.closest !== "function") {
return;
}
const pop = target.closest(".js-pop");
if (pop && pop.querySelector("a[href]")) {
e.stopPropagation();
}
},
true
);
window.addEventListener("pageshow", clean);
document.addEventListener("DOMContentLoaded", clean);
window.addEventListener("load", clean);
setTimeout(clean, 1000);
setTimeout(clean, 3000);
}
if (document.documentElement) {
start();
} else {
document.addEventListener("DOMContentLoaded", start);
}
})();