BetterBoyFriendTV

优化 BoyFriendTV 浏览体验:去除导航栏广告、直播卡片、底栏与边栏冗余区块。

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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);
  }
})();