TBD - Combined Shoutbox Tools

Combined script with Easy mentioning + Shoutbox Notifier + Url Helper.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        TBD - Combined Shoutbox Tools
// @namespace   GTmonkey Scripts
// @match       https://www.torrentbd.com/
// @match       https://www.torrentbd.me/
// @match       https://www.torrentbd.net/
// @match       https://www.torrentbd.org/
// @exclude     *?account-login
// @grant       GM_addStyle
// @grant       GM_setValue
// @grant       GM_getValue
// @version     2.1
// @run-at      document-end
// @author      BENZiN + 5ifty6ix
// @license     MIT
// @description Combined script with Easy mentioning + Shoutbox Notifier + Url Helper.
// @icon       https://www.google.com/s2/favicons?sz=64&domain=torrentbd.net
// ==/UserScript==

// ===== Main Scripts Links ====
// https://greasyfork.org/scripts/546367  &  https://greasyfork.org/scripts/454697
// ==================

;(() => {
  // ===========================
  // CONFIGURATION
  // ===========================
  const enableEasyMention = 1
  const enableUrlBtn = 1
  const enableNotifier = 1

  const processedMessages = window.localStorage.getItem("TBD_processed_messages")
    ? JSON.parse(window.localStorage.getItem("TBD_processed_messages"))
    : []
  const MAX_PROCESSED_MESSAGES = 200

  // ===========================
  // GLOBAL STYLES
  // ===========================
  let css = ""

  // Easy Mention Styles
  const easyMentionStyles = `
        .shout-user {
            user-select: none;
        }
        .chromium span.shout-time:has(+ .shout-user [href^="account"]) {
            cursor: pointer;
            position: relative;
        }
        .chromium span.shout-time:has(+ .shout-user [href^="account"]):hover {
            margin-left: -1.5em;
        }
        .chromium span.shout-time:has(+ .shout-user [href^="account"])::after {
            content: "";
            margin-left: .5em;
            height: 1em;
            rotate: 180deg;
            display: none;
            width: 1em;
        }
        .chromium span.shout-time:has(+ .shout-user [href^="account"]):hover::after {
            display: inline-block;
        }
        .firefox span.shout-time {
            cursor: pointer;
            position: relative;
        }
        .firefox span.shout-time:hover {
            margin-left: -1.5em;
        }
        .firefox span.shout-time::after {
            content: "";
            margin-left: .5em;
            height: 1em;
            rotate: 180deg;
            display: none;
            width: 1em;
        }
        .firefox span.shout-time:hover::after {
            display: inline-block;
        }
        .dark-scheme.chromium span.shout-time:has(+ .shout-user [href^="account"])::after {
            background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(238, 238, 238);"/></svg>');
        }
        .light-scheme.chromium span.shout-time:has(+ .shout-user [href^="account"])::after {
            background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(68 ,68, 68);"/></svg>');
        }
        .dark-scheme.firefox span.shout-time::after {
            background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(238, 238, 238);"/></svg>');
        }
        .light-scheme.firefox span.shout-time::after {
            background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M205 34.8c11.5 5.1 19 16.6 19 29.2v64H336c97.2 0 176 78.8 176 176c0 113.3-81.5 163.9-100.2 174.1c-2.5 1.4-5.3 1.9-8.1 1.9c-10.9 0-19.7-8.9-19.7-19.7c0-7.5 4.3-14.4 9.8-19.5c9.4-8.8 22.2-26.4 22.2-56.7c0-53-43-96-96-96H224v64c0 12.6-7.4 24.1-19 29.2s-25 3-34.4-5.4l-160-144C3.9 225.7 0 217.1 0 208s3.9-17.7 10.6-23.8l160-144c9.4-8.5 22.9-10.6 34.4-5.4z" style="fill: rgb(68 ,68, 68);"/></svg>');
        }
    `

  // URL Button Styles
  const urlBtnStyles = `
        #shout-ibb-container {
            display: flex;
            gap: 0 .5rem;
            margin-right: 0;
            padding-right: .5rem;
            right: 0;
            position: absolute;
        }
        #shout-send-container {
            position: relative;
        }
        #urlWindow {
            position: absolute;
            width: 100%;
            left: 0;
            flex-flow: wrap;
            bottom: 7px;
            gap: 0.5rem;
            display: flex;
            transition: visibility 0s linear .2s, opacity .1s linear .1s, translate .2s linear;
            visibility: hidden;
            background: var(--main-bg);
            border-top: 1px solid var(--border-color);
            border-bottom: 1px solid var(--border-color);
            padding-top: 0.5rem;
            padding-bottom: 0.5rem;
            opacity: 0;
        }
        .spotlight #urlWindow {
            bottom: 28px;
        }
        #urlWindow.show {
            visibility: visible;
            translate: 0 -30px;
            transition-delay: 0s;
            opacity: 1;
        }
        .url-inputs {
            height: 37px;
            color: var(--text-color) !important;
            background: var(--main-bg);
            padding: 0px .5rem;
            border-top: 1px solid var(--border-color);
            border-bottom: 1px solid var(--border-color);
            border-left: none;
            border-right: none;
            outline: 0;
        }
        .spotlight .url-inputs {
            height: 60px;
        }
        #urlField {
            flex: 1 1 100%;
        }
        #labelField {
            flex: 4 1 auto;
            border-right: 1px solid var(--border-color);
        }
        #submitURL {
            flex: 1 1 auto;
            background: transparent;
            border: 1px solid var(--border-color);
            border-right: none;
            color: var(--text-color);
            font-weight: 600;
            font-size: 0.9rem;
        }
        #urlBtn i {
            line-height: 37px;
            font-size: 0.9rem;
            font-weight: 600;
            font-family: inherit;
            user-select: none;
        }
        input.shoutbox-text {
            width: auto !important;
        }
        input#shout_text {
            padding-right: 170px;
        }
        .spotlight input#shout_text {
            padding-left: .5rem;
            padding-right: calc(220px - .5rem);
        }
        @media(max-width: 767px) {
            .spotlight input#shout_text {
                padding-right: calc(200px - .5rem);
            }
            .spotlight #shout-ibb-container {
                padding-right: .5rem;
            }
        }
    `

  // Notifier Styles
  const notifierStyles = `
        #sbn-modal-wrapper { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.6); display: flex; justify-content: center; align-items: center; backdrop-filter: blur(4px); }
        #sbn-container { background-color: #2c2c2c; color: #e0e0e0; border: 1px solid #4a4a4a; border-radius: 16px; width: 90%; max-width: 420px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); }
        #sbn-header { padding: 24px; text-align: center; position: relative; }
        #sbn-header h1 { font-size: 26px; font-weight: 700; color: #fff; margin: 0 0 8px 0; }
        #sbn-header p { font-size: 14px; color: #a0a0a0; margin: 0; }
        #sbn-close-btn { position: absolute; top: 05px; right: 10px; border: none; background: none; color: #888; cursor: pointer; font-size: 24px; font-weight: bold; transition: color .2s; padding: 4px; line-height: 1; }
        #sbn-close-btn:hover { color: #fff; }
        #sbn-content { padding: 0 24px 24px 24px; }
        .sbn-form-group { margin-bottom: 24px; }
        .sbn-form-group:last-child { margin-bottom: 0; }
        #sbn-content label { display: block; margin-bottom: 8px; font-weight: 500; color: #CFCFCF; font-size: 13px; }
        .sbn-label-row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
        #sbn-reset-keywords { background: none; border: none; color: #FF453A; font-size: 13px; font-weight: 500; cursor: pointer; padding: 0; }
        #sbn-reset-keywords:hover { text-decoration: underline; }
        #sbn-username-display { background-color: #1e1e1e; border: 1px solid #555; border-radius: 8px; color: #fff; padding: 12px 16px; font-size: 16px; min-height: 45px; box-sizing: border-box; }
        #sbn-keywords { width: 100%; height: 180px; resize: none; background-color: #1e1e1e; border: 1px solid #555; border-radius: 8px; color: #fff; padding: 10px; font-size: 14px; box-sizing: border-box; }
        #sbn-keywords:focus { outline: none; border-color: #888; }
        .sbn-controls-row { display: grid; grid-template-columns: 1fr 1.5fr; gap: 24px; }
        #sbn-color-picker-wrapper { position: relative; width: 100%; height: 44px; border: 1px solid #555; border-radius: 8px; overflow: hidden; }
        #sbn-color { position: absolute; top: -5px; left: -5px; width: calc(100% + 10px); height: calc(100% + 10px); border: none; padding: 0; cursor: pointer; }
        .sbn-volume-control { display: flex; align-items: center; gap: 12px; height: 44px; background-color: #1e1e1e; border: 1px solid #555; border-radius: 8px; padding: 0 20px; box-sizing: border-box; }
        #sbn-volume-icon { color: #a0a0a0; width: 24px; height: 24px; flex-shrink: 0; }
        #sbn-volume { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; background: #4a4a4a; border-radius: 3px; outline: none; }
        #sbn-volume::-webkit-slider-runnable-track { background: #4a4a4a; border-radius: 3px; height: 7px; margin-right: -5px; margin-left: -5px; }
        #sbn-volume::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; background: #d1d5db; border-radius: 50%; cursor: pointer; margin-top: -6px; }
        #sbn-volume::-moz-range-track { background: #4a4a4a; border-radius: 3px; height: 6px; }
        #sbn-volume::-moz-range-thumb { width: 18px; height: 18px; background: #d1d5db; border-radius: 50%; cursor: pointer; border: none; }
        #sbn-settings-btn { cursor: pointer; margin-left: 10px; display: inline-flex; align-items: center; color: #9e9e9e; transition: color .2s ease; }
        #sbn-settings-btn:hover { color: #fff; }
        #sbn-settings-btn svg { width: 20px; height: 20px; }
    `

  css += easyMentionStyles + urlBtnStyles + notifierStyles

  // ===========================
  // DETECTOR FUNCTION
  // ===========================
  function detectUsername() {
    const allRankElements = document.querySelectorAll(".tbdrank")
    for (const rankElement of allRankElements) {
      if (!rankElement.closest("#shoutbox-container")) {
        if (rankElement && rankElement.firstChild && rankElement.firstChild.nodeType === Node.TEXT_NODE) {
          return rankElement.firstChild.nodeValue.trim()
        }
      }
    }
    return "Not Found"
  }

  // ===========================
  // NOTIFIER SETTINGS
  // ===========================
  const settings = {
    username: window.localStorage.getItem("TBD_shout_username") || detectUsername() || "",
    keywords: window.localStorage.getItem("TBD_shout_keywords")
      ? JSON.parse(window.localStorage.getItem("TBD_shout_keywords"))
      : [],
    soundVolume: window.localStorage.getItem("TBD_shout_soundVolume")
      ? Number.parseFloat(window.localStorage.getItem("TBD_shout_soundVolume"))
      : 0.5,
    highlightColor: window.localStorage.getItem("TBD_shout_highlightColor") || "#2E4636",
  }

  // ===========================
  // NOTIFIER FUNCTIONS
  // ===========================
  function playSound() {
    if (settings.soundVolume < 0.01) return
    try {
      const audio = new Audio(
        "https://raw.githubusercontent.com/5ifty6ix/custom-sounds/refs/heads/main/new-notification-010-352755.mp3",
      )
      audio.volume = settings.soundVolume
      audio.play()
    } catch (e) {
      console.error("Shoutbox Notifier: Could not play custom sound.", e)
    }
  }

  function notifyUser() {
    if (!document.title.startsWith("(1)")) {
      document.title = "(1) " + document.title
    }
    playSound()
  }

  function highlightShout(shoutElement) {
    shoutElement.style.backgroundColor = settings.highlightColor
    shoutElement.style.borderLeft = "3px solid #14a76c"
    shoutElement.style.paddingLeft = "5px"
  }

  function checkShout(shoutElement) {
    if (!shoutElement || !shoutElement.id) return
    const messageBody = shoutElement.querySelector(".shout-text")
    if (!messageBody) return
    const messageText = messageBody.textContent.toLowerCase()
    const activeUsername = settings.username && settings.username !== "Not Found" ? [settings.username] : []
    const allKeywords = [...activeUsername, ...settings.keywords]
      .filter((k) => k && k.trim() !== "")
      .map((k) => k.toLowerCase())
    if (allKeywords.length === 0) return
    let keywordFound = false
    for (const keyword of allKeywords) {
      const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
      const keywordRegex = new RegExp(`\\b${escapeRegExp(keyword)}\\b`, "i")
      if (keywordRegex.test(messageText)) {
        highlightShout(shoutElement)
        keywordFound = true
        break
      }
    }
    if (keywordFound) {
      if (!processedMessages.includes(shoutElement.id)) {
        notifyUser()
        processedMessages.push(shoutElement.id)
        if (processedMessages.length > MAX_PROCESSED_MESSAGES) {
          processedMessages.shift()
        }
        window.localStorage.setItem("TBD_processed_messages", JSON.stringify(processedMessages))
      }
    }
  }

  // ===========================
  // EASY MENTION FUNCTION
  // ===========================
  function easyMention() {
    if (window.location.pathname !== "/") return
    const shout = document.querySelector("#shout_text")
    if (navigator.vendor == "") {
      if (!document.body.classList.contains("firefox")) document.body.classList.add("firefox")
    } else {
      if (!document.body.classList.contains("chromium")) document.body.classList.add("chromium")
    }
    let name

    document.addEventListener("click", (e) => {
      if (document.body.classList.contains("chromium")) {
        if (!e.target.matches(".chromium .shout-time:has(+ .shout-user [href^='account'])")) return
      } else if (document.body.classList.contains("firefox")) {
        if (!e.target.matches(".firefox .shout-time")) return
      }
      if ((name = e.target.nextElementSibling.querySelector('[href^="account"] span'))) {
        if (shout.value != "" && shout.value.slice(-1) != " ") shout.value += " "
        shout.value += "@" + name.innerText.trim() + " "
      }
    })
  }

  // ===========================
  // URL BUTTON FUNCTION
  // ===========================
  function urlBtn() {
    if (window.location.pathname !== "/") return
    if (window.location.search.includes("spotlight")) document.body.classList.add("spotlight")

    const shout = document.querySelector("#shout_text")
    const ibbCont = document.querySelector("#shout-ibb-container")
    const shoutCont = document.querySelector("#shout-send-container")

    const urlWindow = document.createElement("div")
    urlWindow.id = "urlWindow"
    shoutCont.appendChild(urlWindow)

    const showUrlWindow = () => {
      urlWindow.classList.add("show")
      shoutCont.querySelectorAll("[id^='spotlight']").forEach((spotlight) => {
        if (spotlight.classList.contains("shiner")) {
          spotlight.classList.remove("shiner")
          spotlight.classList.add("fader")
        }
      })
    }

    const hideUrlWindow = () => urlWindow.classList.remove("show")

    const toggleUrlWindow = () => {
      if (urlWindow.classList.contains("show")) {
        hideUrlWindow()
      } else {
        showUrlWindow()
      }
    }

    const initHeight = window.innerHeight
    window.onresize = () => {
      if (window.innerHeight < initHeight && urlWindow.classList.contains("show")) shoutCont.scrollIntoView(false)
    }

    const urlField = document.createElement("input")
    urlField.id = "urlField"
    urlField.classList.add("url-inputs")
    urlField.placeholder = "URL"
    urlWindow.appendChild(urlField)
    urlField.onmouseover = () => urlField.focus()

    const labelField = document.createElement("input")
    labelField.id = "labelField"
    labelField.classList.add("url-inputs")
    labelField.placeholder = "Label (Optional)"
    urlWindow.appendChild(labelField)

    const submitURL = document.createElement("input")
    submitURL.type = "button"
    submitURL.id = "submitURL"
    submitURL.value = "Submit"
    urlWindow.appendChild(submitURL)

    const urlBtn = document.createElement("span")
    urlBtn.id = "urlBtn"
    urlBtn.innerHTML = `<i class="material-icons">URL</i>`
    urlBtn.classList.add("inline-submit-btn")
    ibbCont.insertBefore(urlBtn, ibbCont.childNodes[4])

    document.addEventListener("click", (e) => {
      if (!e.target.matches("#urlBtn i")) return
      if (shout.value != "") shout.value += " "
      toggleUrlWindow()
      urlField.focus()
    })

    document.addEventListener("click", (e) => {
      if (!e.target.matches(".inline-submit-btn:not(#urlBtn) i")) return
      hideUrlWindow()
    })

    const clearFields = () => {
      urlField.value = ""
      labelField.value = ""
    }

    const urlTagCreate = () => {
      const rawURL = urlField.value.trim()
      const label = labelField.value

      if (rawURL.length > 150) return "URL is too long.\nConsider shortening it using URL shorteners."
      if (!/^https:\/\//i.test(rawURL)) return "Please enter a safe https URL."
      if (!/^https:\/\/.*\./i.test(rawURL)) return "Please make sure the URL is correct."

      if (shout.value != "" && shout.value.slice(-1) != " ") shout.value += " "

      if (label != "") {
        shout.value += `[url=${rawURL}]${label}[/url] `
      } else {
        shout.value += `[url]${rawURL}[/url] `
      }
      hideUrlWindow()
      clearFields()
      shout.focus()
    }

    submitURL.onclick = () => {
      const error = urlTagCreate()
      if (typeof error != "undefined" || error != null) {
        alert(error)
        clearFields()
      }
    }

    document.addEventListener("keypress", (e) => {
      if (e.target.matches(".url-inputs")) {
        if (e.key === "Enter") {
          const error = urlTagCreate()
          if (typeof error != "undefined" || error != null) {
            alert(error)
            clearFields()
          }
        }
      }
    })
  }

  // ===========================
  // SETTINGS UI
  // ===========================
  function createSettingsUI() {
    const uiWrapper = document.createElement("div")
    uiWrapper.id = "sbn-modal-wrapper"
    uiWrapper.style.display = "none"
    uiWrapper.innerHTML = `
            <div id="sbn-container">
                <div id="sbn-header">
                    <h1>Shoutbox Notifier</h1>
                    <p>Get notified when your username or any specific words appear in the Shoutbox</p>
                    <button id="sbn-close-btn" title="Close">×</button>
                </div>
                <div id="sbn-content">
                    <div class="sbn-form-group">
                        <label>Username</label>
                        <div id="sbn-username-display">Detecting...</div>
                    </div>
                    <div class="sbn-form-group">
                        <div class="sbn-label-row">
                            <label for="sbn-keywords">Other Keywords (One per line)</label>
                            <button id="sbn-reset-keywords">Reset</button>
                        </div>
                        <textarea id="sbn-keywords" placeholder="Add other keywords here..."></textarea>
                    </div>
                    <div class="sbn-controls-row">
                        <div class="sbn-form-group">
                            <label>Highlight Colour</label>
                            <div id="sbn-color-picker-wrapper">
                                <input type="color" id="sbn-color">
                            </div>
                        </div>
                        <div class="sbn-form-group">
                            <label for="sbn-volume">Notification Volume</label>
                            <div class="sbn-volume-control">
                                <div id="sbn-volume-icon"></div>
                                <input type="range" id="sbn-volume" min="0" max="100" step="1">
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        `
    document.body.appendChild(uiWrapper)

    const settingsButton = document.createElement("div")
    settingsButton.id = "sbn-settings-btn"
    settingsButton.title = "Shoutbox Notifier Settings"
    settingsButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill-rule="evenodd" d="M11.078 2.25c-.917 0-1.699.663-1.85 1.567L9.05 4.889c-.02.12-.115.26-.297.348a7.493 7.493 0 0 0-.986.57c-.166.115-.334.126-.45.083L6.3 5.508a1.875 1.875 0 0 0-2.282.819l-.922 1.597a1.875 1.875 0 0 0 .432 2.385l.84.692c.095.078.17.229.154.43a7.598 7.598 0 0 0 0 1.139c.015.2-.059.352-.153.43l-.841.692a1.875 1.875 0 0 0-.432 2.385l.922 1.597a1.875 1.875 0 0 0 2.282.818l1.019-.382c.115-.043.283-.031.45.082.312.214.641.405.985.57.182.088.277.228.297.35l.178 1.071c.151.904.933 1.567 1.85 1.567h1.844c.916 0 1.699-.663 1.85-1.567l.178-1.072c.02-.12.114-.26.297-.349.344-.165.673-.356.985-.57.167-.114.335-.125.45-.082l1.02.382a1.875 1.875 0 0 0 2.28-.819l.923-1.597a1.875 1.875 0 0 0-.432-2.385l-.84-.692c-.095-.078-.17-.229-.154-.43a7.614 7.614 0 0 0 0-1.139c-.016-.2.059-.352.153-.43l.84-.692c.708-.582.891-1.59.433-2.385l-.922-1.597a1.875 1.875 0 0 0-2.282-.818l-1.02.382c-.114.043-.282.031-.449-.083a7.49 7.49 0 0 0-.985-.57c-.183-.087-.277-.227-.297-.348l-.179-1.072a1.875 1.875 0 0 0-1.85-1.567h-1.843ZM12 15.75a3.75 3.75 0 1 0 0-7.5 3.75 3.75 0 0 0 0 7.5Z" clip-rule="evenodd" /></svg>`
    settingsButton.addEventListener("click", (e) => {
      e.stopPropagation()
      uiWrapper.style.display = "flex"
      loadSettings()
    })

    const titleElement = document.querySelector("#shoutbox-container .content-title h6.left")
    if (titleElement) {
      titleElement.style.display = "flex"
      titleElement.style.alignItems = "center"
      titleElement.appendChild(settingsButton)
    }

    const usernameDisplay = document.getElementById("sbn-username-display")
    const keywordsInput = document.getElementById("sbn-keywords")
    const resetKeywordsBtn = document.getElementById("sbn-reset-keywords")
    const colorInput = document.getElementById("sbn-color")
    const colorPickerWrapper = document.getElementById("sbn-color-picker-wrapper")
    const volumeSlider = document.getElementById("sbn-volume")
    const volumeIconContainer = document.getElementById("sbn-volume-icon")
    const closeBtn = document.getElementById("sbn-close-btn")

    function loadSettings() {
      const detectedUser = detectUsername()
      settings.username = detectedUser
      window.localStorage.setItem("TBD_shout_username", settings.username)
      usernameDisplay.textContent = settings.username
      settings.keywords = window.localStorage.getItem("TBD_shout_keywords")
        ? JSON.parse(window.localStorage.getItem("TBD_shout_keywords"))
        : []
      settings.highlightColor = window.localStorage.getItem("TBD_shout_highlightColor") || "#2E4636"
      settings.soundVolume = window.localStorage.getItem("TBD_shout_soundVolume")
        ? Number.parseFloat(window.localStorage.getItem("TBD_shout_soundVolume"))
        : 0.5
      keywordsInput.value = settings.keywords.join("\n")
      colorInput.value = settings.highlightColor
      colorPickerWrapper.style.backgroundColor = settings.highlightColor
      volumeSlider.value = settings.soundVolume * 100
      updateVolumeIcon()
    }

    const volumeIcons = {
      mute: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 4.06c0-1.336-1.616-2.005-2.56-1.06l-4.5 4.5H4.508c-1.141 0-2.318.664-2.66 1.905A9.76 9.76 0 0 0 1.5 12c0 .898.121 1.768.35 2.595.341 1.24 1.518 1.905 2.659 1.905h1.93l4.5 4.5c.945.945 2.561.276 2.561-1.06V4.06ZM17.78 9.22a.75.75 0 1 0-1.06 1.06L18.44 12l-1.72 1.72a.75.75 0 1 0 1.06 1.06l1.72-1.72 1.72 1.72a.75.75 0 1 0 1.06-1.06L20.56 12l1.72-1.72a.75.75 0 1 0-1.06-1.06l-1.72 1.72-1.72-1.72Z" /></svg>`,
      on: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.5 4.06c0-1.336-1.616-2.005-2.56-1.06l-4.5 4.5H4.508c-1.141 0-2.318.664-2.66 1.905A9.76 9.76 0 0 0 1.5 12c0 .898.121 1.768.35 2.595.341 1.24 1.518 1.905 2.659 1.905h1.93l4.5 4.5c.945.945 2.561.276 2.561-1.06V4.06ZM18.584 5.106a.75.75 0 0 1 1.06 0c3.808 3.807 3.808 9.98 0 13.788a.75.75 0 0 1-1.06-1.06 8.25 8.25 0 0 0 0-11.668.75.75 0 0 1 0-1.06Z" /><path d="M15.932 7.757a.75.75 0 0 1 1.061 0 6 6 0 0 1 0 8.486.75.75 0 0 1-1.06-1.061 4.5 4.5 0 0 0 0-6.364.75.75 0 0 1 0-1.06Z" /></svg>`,
    }

    function updateVolumeIcon() {
      const value = Number.parseFloat(volumeSlider.value)
      if (value == 0) {
        volumeIconContainer.innerHTML = volumeIcons.mute
      } else {
        volumeIconContainer.innerHTML = volumeIcons.on
      }
    }

    closeBtn.addEventListener("click", () => {
      uiWrapper.style.display = "none"
    })
    uiWrapper.addEventListener("click", (event) => {
      if (event.target.id === "sbn-modal-wrapper") {
        uiWrapper.style.display = "none"
      }
    })

    resetKeywordsBtn.addEventListener("click", () => {
      keywordsInput.value = ""
      settings.keywords = []
      window.localStorage.setItem("TBD_shout_keywords", JSON.stringify(settings.keywords))
    })

    keywordsInput.addEventListener("input", () => {
      settings.keywords = keywordsInput.value
        .split("\n")
        .map((k) => k.trim())
        .filter((k) => k)
      window.localStorage.setItem("TBD_shout_keywords", JSON.stringify(settings.keywords))
    })

    colorInput.addEventListener("input", () => {
      settings.highlightColor = colorInput.value
      colorPickerWrapper.style.backgroundColor = colorInput.value
      window.localStorage.setItem("TBD_shout_highlightColor", settings.highlightColor)
    })

    volumeSlider.addEventListener("input", () => {
      settings.soundVolume = Number.parseFloat(volumeSlider.value) / 100
      window.localStorage.setItem("TBD_shout_soundVolume", settings.soundVolume)
      updateVolumeIcon()
    })

    loadSettings()
  }

  // ===========================
  // INITIALIZE ALL FEATURES
  // ===========================
  window.addEventListener("load", () => {
    if (enableEasyMention) easyMention()
    if (enableUrlBtn) urlBtn()

    if (enableNotifier) {
      const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
          if (mutation.addedNodes.length) {
            mutation.addedNodes.forEach((node) => {
              if (node.nodeType === 1 && node.classList.contains("shout-item")) {
                checkShout(node)
              }
            })
          }
        }
      })

      function startObserver() {
        const shoutbox = document.getElementById("shouts-container")
        if (shoutbox) {
          shoutbox.querySelectorAll(".shout-item").forEach(checkShout)
          observer.observe(shoutbox, {
            childList: true,
          })
        } else {
          setTimeout(startObserver, 500)
        }
      }

      createSettingsUI()
      startObserver()
    }
  })

  // Apply styles
  if (typeof css !== "undefined" || css !== null) {
    const styleNode = document.createElement("style")
    styleNode.appendChild(document.createTextNode(css))
    ;(document.querySelector("head") || document.documentElement).appendChild(styleNode)
  }

  // Extra features
  document.querySelector("#shout_text")?.setAttribute("autocapitalize", "on")
  document
    .querySelector("meta[name='viewport']")
    ?.setAttribute(
      "content",
      "width=device-width, height=device-height, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no",
    )
})()