// ==UserScript== // @name Sinflix Modifier // @namespace https://greasyfork.org/en/users/1490967-asurpbs // @version 26.4.17.1 // @description Enhances SinFlix pages with Google & MyDramaList search icons, BuzzHeavier ID auto-linking, back-to-top button, inline search, customizable section ordering, and a SinFlix chat button. On pst.moe: clickable links, copy-all-links per resolution, and Mega.nz bypass circles (click to instantly bypass & download, or copy all bypass links). On mega.nz file pages: floating bypass download button that skips Mega quota limits. // @license MIT // @author asurpbs // @match https://rentry.co/sin-flix // @match https://text.is/Sinflix // @match https://pst.moe/paste/* // @match https://buzzheavier.com/* // @match https://mega.nz/file/* // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_xmlhttpRequest // @run-at document-start // @updateURL https://raw.githubusercontent.com/mthlpbs/sinflix-modifier/refs/heads/main/sinflix-modifier.user.js // @downloadURL https://raw.githubusercontent.com/mthlpbs/sinflix-modifier/refs/heads/main/sinflix-modifier.user.js // @copyright 2026, mthlpbs (https://greasyfork.org/en/users/1490967-asurpbs) // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const config = { showGoogleCircle: GM_getValue('showGoogleCircle', true), showMdlCircle: GM_getValue('showMdlCircle', true), convertBuzzheavierLinks: GM_getValue('convertBuzzheavierLinks', true), showBackToTopButton: GM_getValue('showBackToTopButton', true), // NEW: Add setting for link opening style, defaulting to 'popup' linkOpenStyle: GM_getValue('linkOpenStyle', 'popup'), // NEW: Add setting for moving Currently Airing to top moveCurrentlyAiringToTop: GM_getValue('moveCurrentlyAiringToTop', false), // NEW: Add setting for SinFlix chat box button showChatBoxButton: GM_getValue('showChatBoxButton', true), // NEW: Add setting for chat box opening style, defaulting to 'tab' chatBoxOpenStyle: GM_getValue('chatBoxOpenStyle', 'popup'), // NEW: pst.moe enhancements pstMoeEnhancements: GM_getValue('pstMoeEnhancements', true), // NEW: Google search keyword suffix googleSearchSuffix: GM_getValue('googleSearchSuffix', 'TV Series'), // NEW: Buzzheavier enhancements buzzheavierEnhancements: GM_getValue('buzzheavierEnhancements', false), buzzSplitQuality: GM_getValue('buzzSplitQuality', false), buzzDirectDownload: GM_getValue('buzzDirectDownload', false), buzzCopyLinks: GM_getValue('buzzCopyLinks', false), // NEW: Mega.nz bypass megaBypass: GM_getValue('megaBypass', true), // NEW: Mega.nz floating download button megaNzButton: GM_getValue('megaNzButton', true), // NEW: Top search bar with Dynamic Island animation showTopSearchBar: GM_getValue('showTopSearchBar', true) }; // --- Style Definitions --- GM_addStyle(` /* --- Buzzheavier Tool Buttons --- */ .bh-actions { display: inline-flex; gap: 4px; margin-left: 12px; vertical-align: middle; opacity: 0.7; transition: opacity 0.2s ease; } .bh-actions.single-page { opacity: 0.9; margin-left: 8px; } .bh-actions.single-page:hover { opacity: 1; } tr.editable:hover .bh-actions { opacity: 1; } .bh-btn { cursor: pointer; border: none; background: transparent; padding: 4px; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: inherit; transition: all 0.2s ease; } .bh-actions.single-page .bh-btn { color: #ccc; padding: 6px; } .bh-btn:hover { background-color: rgba(255, 255, 255, 0.15); transform: scale(1.1); color: #fff; box-shadow: 0 0 8px rgba(0,0,0,0.2); } .bh-btn.play-btn:hover { color: #4ade80; } .bh-btn.copy-btn:hover { color: #60a5fa; } .bh-btn.dl-btn:hover { color: #f472b6; } .bh-btn svg { width: 18px; height: 18px; fill: currentColor; stroke: currentColor; stroke-width: 0; } .bh-actions.single-page .bh-btn svg { width: 20px; height: 20px; } .bh-btn.loading svg { animation: spin 0.8s linear infinite; fill: #fbbf24; } @keyframes spin { 100% { transform: rotate(360deg); } } /* --- pst.moe Enhancements --- */ .sinflix-res-header { display: inline-flex; align-items: center; gap: 10px; font-weight: bold; margin: 8px 0; } .sinflix-copy-btn { background: #2a2b2c; color: #e8eaed; border: 1px solid #5f6368; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 11px; font-family: inherit; transition: all 0.2s; } .sinflix-copy-btn:hover { background: #1a73e8; border-color: #1a73e8; color: white; } /* --- Mega Bypass Circle --- */ .sinflix-mega-circle { display: inline-block; width: 13px; height: 13px; border-radius: 50%; background: #9aa0a6; cursor: pointer; vertical-align: middle; margin-left: 5px; opacity: 0.5; transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease; flex-shrink: 0; } .sinflix-mega-circle:hover { opacity: 1; transform: scale(1.25); background: #34a853; } .sinflix-mega-bypass-header { display: inline-flex; align-items: center; gap: 10px; margin: 6px 0; } .sinflix-copy-bypass-btn { background: #2a2b2c; color: #e8eaed; border: 1px solid #9aa0a6; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 11px; font-family: inherit; transition: all 0.2s; } .sinflix-copy-bypass-btn:hover { background: #34a853; border-color: #34a853; color: white; } /* --- FileDitch download circle --- */ .sinflix-fd-dl-circle { display: inline-block; width: 13px; height: 13px; border-radius: 50%; background: #f97316; cursor: pointer; vertical-align: middle; margin-left: 4px; opacity: 0.55; transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease; flex-shrink: 0; } .sinflix-fd-dl-circle:hover { opacity: 1; transform: scale(1.3); background: #fb923c; } .sinflix-fd-dl-circle.fd-loading { opacity: 0.35; cursor: wait; animation: fd-pulse 0.9s ease-in-out infinite alternate; } @keyframes fd-pulse { from { opacity: 0.25; transform: scale(0.85); } to { opacity: 0.65; transform: scale(1.05); } } /* --- Prevent section flash on load --- */ .entry-text article h4 { opacity: 0; transition: opacity 0.1s ease-in-out; } .entry-text article h4.sinflix-visible { opacity: 1; } .entry-text article.sinflix-processed h4 { opacity: 1; } /* --- Rentry content box rounded corners --- */ .col-12.long-words { border-radius: 12px; overflow: hidden; } .entry-text { border-radius: 12px; overflow: hidden; } /* --- Settings Button --- */ /* Settings button is inside the search capsule when capsule is ON */ #kdrama-settings-button { position: fixed; top: 20px; right: 20px; width: 40px; height: 40px; z-index: 10001; background: rgba(50, 50, 50, 0.4); backdrop-filter: blur(6px); border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.15); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.3s ease; } #kdrama-settings-button:hover { background: rgba(80, 80, 80, 0.6); } /* --- Modal Styles --- */ #kdrama-settings-modal { display: none; position: fixed; z-index: 10002; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); backdrop-filter: blur(4px); justify-content: center; align-items: center; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; opacity: 0; transition: opacity 0.2s ease-out; } #kdrama-settings-modal.show { opacity: 1; } .kdrama-modal-content { background: #202124; color: #e8eaed; padding: 0; border-radius: 8px; width: 95%; max-width: 520px; max-height: 90vh; box-shadow: 0 8px 32px rgba(0,0,0,0.6); border: 1px solid #3c4043; position: relative; overflow: hidden; transform: scale(0.9); transition: transform 0.2s ease-out; } #kdrama-settings-modal.show .kdrama-modal-content { transform: scale(1); } .kdrama-modal-header { background: #2d2e30; color: #e8eaed; padding: 16px 20px; border-radius: 8px 8px 0 0; position: relative; border-bottom: 1px solid #3c4043; } .kdrama-modal-header h2 { margin: 0; font-size: 18px; font-weight: 500; display: flex; align-items: center; gap: 8px; color: #e8eaed; } .kdrama-modal-header .header-icon { width: 20px; height: 20px; } #kdrama-settings-close { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); width: 28px; height: 28px; border-radius: 4px; background: transparent; border: none; color: #9aa0a6; font-size: 18px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: background 0.2s ease, color 0.2s ease; } #kdrama-settings-close:hover { background: #3c4043; color: #e8eaed; } .kdrama-modal-body { padding: 20px; max-height: 60vh; overflow-y: auto; background: #202124; } .kdrama-modal-body::-webkit-scrollbar { width: 8px; } .kdrama-modal-body::-webkit-scrollbar-track { background: #2d2e30; border-radius: 4px; } .kdrama-modal-body::-webkit-scrollbar-thumb { background: #5f6368; border-radius: 4px; } .kdrama-modal-body::-webkit-scrollbar-thumb:hover { background: #80868b; } /* Settings Sections */ .kdrama-settings-section { margin-bottom: 32px; } .kdrama-settings-section:last-child { margin-bottom: 0; } .kdrama-section-title { font-size: 16px; font-weight: 500; color: #9aa0a6; margin: 0 0 12px 0; display: flex; align-items: center; gap: 8px; text-transform: uppercase; letter-spacing: 0.5px; } .kdrama-section-title .section-icon { width: 16px; height: 16px; } .kdrama-toggle-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: #2d2e30; border-radius: 6px; margin-bottom: 8px; border: 1px solid #3c4043; transition: all 0.2s ease; } .kdrama-toggle-item:last-child { margin-bottom: 0; } .kdrama-toggle-item:hover { background: #35363a; border-color: #5f6368; } .kdrama-toggle-info { flex: 1; } .kdrama-toggle-label { font-size: 14px; font-weight: 500; margin: 0 0 2px 0; color: #e8eaed; } .kdrama-toggle-description { font-size: 12px; color: #9aa0a6; margin: 0; line-height: 1.3; } /* Custom Toggle Switch */ .kdrama-toggle-switch { position: relative; width: 50px; height: 26px; margin-left: 16px; } .kdrama-toggle-switch input { opacity: 0; width: 0; height: 0; } .kdrama-toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #5f6368; transition: 0.3s; border-radius: 26px; } .kdrama-toggle-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 4px; bottom: 4px; background-color: #ffffff; transition: 0.3s; border-radius: 50%; box-shadow: 0 2px 4px rgba(0,0,0,0.3); } .kdrama-toggle-switch input:checked + .kdrama-toggle-slider { background: #1a73e8; } .kdrama-toggle-switch input:checked + .kdrama-toggle-slider:before { transform: translateX(24px); } .kdrama-text-input { width: 100%; background: #3c4043; border: 1px solid #5f6368; color: #e8eaed; padding: 8px 12px; border-radius: 4px; font-size: 14px; box-sizing: border-box; transition: all 0.2s ease; font-family: inherit; } .kdrama-text-input:focus { outline: none; border-color: #1a73e8; background: #202124; } .kdrama-radio-group { background: #2d2e30; border-radius: 6px; padding: 16px; border: 1px solid #3c4043; margin-bottom: 16px; } .kdrama-radio-group-title { font-size: 14px; font-weight: 500; color: #e8eaed; margin: 0 0 12px 0; } .kdrama-radio-options { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; } .kdrama-radio-option { position: relative; } .kdrama-radio-option input[type="radio"] { opacity: 0; position: absolute; width: 100%; height: 100%; margin: 0; cursor: pointer; } .kdrama-radio-option-label { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: #3c4043; border: 1px solid #5f6368; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; font-size: 13px; font-weight: 400; color: #e8eaed; } .kdrama-radio-option input:checked + .kdrama-radio-option-label { background: #1a73e8; border-color: #1a73e8; color: white; } .kdrama-radio-option-label:hover { background: #484a4d; border-color: #80868b; } .kdrama-radio-option input:checked + .kdrama-radio-option-label:hover { background: #1557b0; border-color: #1557b0; } .kdrama-radio-option-icon { width: 14px; height: 14px; } .kdrama-modal-footer { padding: 16px 20px; background: #2d2e30; border-radius: 0 0 8px 8px; border-top: 1px solid #3c4043; } #kdrama-save-button { background: #1a73e8; color: white; padding: 10px 20px; font-size: 14px; font-weight: 500; border-radius: 4px; border: none; cursor: pointer; width: 100%; transition: background 0.2s ease; } #kdrama-save-button:hover { background: #1557b0; } #kdrama-save-button:active { background: #1142a0; } .kdrama-circle-container { display: inline-flex; align-items: center; gap: 3px; margin-right: 5px; vertical-align: middle; position: relative; top: -1px; } .kdrama-circle { display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; border-radius: 7px; border: none; cursor: pointer; font-size: 10px; font-weight: 700; font-family: "Segoe UI", system-ui, sans-serif; letter-spacing: -0.2px; line-height: 1; opacity: 0.28; transition: opacity 0.18s ease, transform 0.18s ease, background 0.18s ease, box-shadow 0.18s ease; user-select: none; flex-shrink: 0; } .kdrama-circle:hover { opacity: 1; transform: scale(1.18); } .google-circle { background: rgba(66, 133, 244, 0.15); color: #4285f4; border: 1px solid rgba(66, 133, 244, 0.3); } .google-circle::after { content: "G"; } .google-circle:hover { background: #4285f4; color: #fff; border-color: #4285f4; box-shadow: 0 2px 8px rgba(66, 133, 244, 0.45); } .mdl-circle { background: rgba(0, 150, 136, 0.12); color: #00897b; border: 1px solid rgba(0, 150, 136, 0.28); } .mdl-circle::after { content: "M"; } .mdl-circle:hover { background: #00897b; color: #fff; border-color: #00897b; box-shadow: 0 2px 8px rgba(0, 150, 136, 0.45); } /* --- Floating Buttons (Back to Top & Search) --- */ .kdrama-float-button { position: fixed; width: 44px; height: 44px; border-radius: 50%; border: 1px solid rgba(255, 255, 255, 0.2) !important; outline: none !important; background: rgba(30, 30, 30, 0.4); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; z-index: 10003; user-select: none; transition: background-color 0.3s ease, opacity 0.3s ease, border 0.3s ease, bottom 0.5s cubic-bezier(0.4, 0.0, 0.2, 1); opacity: 0; pointer-events: none; right: 20px; } .kdrama-float-button.show { opacity: 1; pointer-events: auto; /* Enable interaction when shown */ } .kdrama-float-button:hover, .kdrama-float-button:focus, .kdrama-float-button:active { background: rgba(50, 50, 50, 0.6); border: 1px solid rgba(255, 255, 255, 0.35) !important; outline: none !important; transform: scale(1.05); transition: background-color 0.3s ease, opacity 0.3s ease, border 0.3s ease, bottom 0.5s cubic-bezier(0.4, 0.0, 0.2, 1), transform 0.2s ease; } #kdrama-back-to-top { bottom: 30px; } #kdrama-search-button { bottom: 30px; /* Will be dynamically positioned */ } #kdrama-chat-button { bottom: 84px; /* Will be dynamically positioned */ } /* --- Floating Notification --- */ .kdrama-notification { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.9); color: white; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); z-index: 10005; font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; font-size: 14px; opacity: 0; transition: opacity 0.3s ease, transform 0.3s ease; pointer-events: none; backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.1); } .kdrama-notification.show { opacity: 1; transform: translateX(-50%) translateY(10px); } .kdrama-notification.success { background: rgba(34, 139, 34, 0.9); border-color: rgba(34, 139, 34, 0.3); } .kdrama-notification.error { background: rgba(220, 20, 60, 0.9); border-color: rgba(220, 20, 60, 0.3); } .kdrama-notification.info { background: rgba(30, 144, 255, 0.9); border-color: rgba(30, 144, 255, 0.3); } /* --- Top Search Bar (Dynamic Island) --- */ #sfx-top-searchbar-wrap { position: fixed; top: 14px; left: 50%; transform: translateX(-50%); z-index: 10006; /* Dynamic Island pill shape */ width: min(560px, calc(100vw - 32px)); height: 48px; border-radius: 28px; background: rgba(18, 18, 22, 0.82); backdrop-filter: blur(20px) saturate(180%); -webkit-backdrop-filter: blur(20px) saturate(180%); border: 1px solid rgba(255,255,255,0.13); box-shadow: 0 4px 32px rgba(0,0,0,0.45), 0 0 0 1px rgba(0,0,0,0.3); display: flex; align-items: center; padding: 0 16px; gap: 10px; overflow: hidden; cursor: text; /* Transition for all morphing */ transition: width 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), height 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), border-radius 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), background 0.4s ease, box-shadow 0.4s ease, top 0.55s cubic-bezier(0.34, 1.38, 0.64, 1), opacity 0.35s ease; } #sfx-top-searchbar-wrap.sfx-collapsed { width: 192px; height: 34px; border-radius: 17px; border: 1px solid rgba(255,255,255,0.07); background: rgba(22, 22, 26, 0.72); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); cursor: pointer; box-shadow: none; padding: 0 14px 0 10px; justify-content: center; gap: 6px; overflow: hidden; } #sfx-top-searchbar-wrap.sfx-collapsed:hover { background: rgba(38, 38, 44, 0.82); border-color: rgba(255,255,255,0.13); transform: translateX(-50%) scale(1.03); } #sfx-top-searchbar-wrap.sfx-expanding { /* Briefly scale up a touch during expansion */ } #sfx-top-search-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; color: rgba(255,255,255,0.55); pointer-events: none; transition: color 0.3s ease; } #sfx-top-searchbar-wrap:hover #sfx-top-search-icon { color: rgba(255,255,255,0.85); } /* Collapsed: icon stays in normal flex flow, centred by justify-content on parent */ #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-icon { color: rgba(255,255,255,0.38); flex-shrink: 0; } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-icon svg { width: 13px; height: 13px; } #sfx-top-searchbar-wrap.sfx-collapsed:hover #sfx-top-search-icon { color: rgba(255,255,255,0.65); } /* Collapsed label */ #sfx-top-search-label { font-size: 12px; font-weight: 400; font-family: "Segoe UI", system-ui, sans-serif; color: rgba(255,255,255,0.32); white-space: nowrap; letter-spacing: 0.3px; /* Hidden in expanded state — use max-width so transition works (auto is not animatable) */ max-width: 0; opacity: 0; overflow: hidden; pointer-events: none; transition: opacity 0.35s ease, max-width 0.45s cubic-bezier(0.34, 1.38, 0.64, 1); } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-label { max-width: 100px; opacity: 1; } #sfx-top-searchbar-wrap.sfx-collapsed:hover #sfx-top-search-label { color: rgba(255,255,255,0.62); } #sfx-top-search-input { flex: 1; background: transparent; border: none; outline: none; color: #fff; font-size: 15px; font-family: "Segoe UI", system-ui, sans-serif; caret-color: #a78bfa; min-width: 0; transition: opacity 0.3s ease, width 0.4s ease; } #sfx-top-search-input::placeholder { color: rgba(255,255,255,0.38); } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-input { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } #sfx-top-search-count { font-size: 12px; color: rgba(255,255,255,0.45); white-space: nowrap; flex-shrink: 0; transition: opacity 0.3s ease, width 0.4s ease; } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-count { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } .sfx-top-nav-btn { background: none; border: none; color: rgba(255,255,255,0.55); width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; transition: background 0.2s, color 0.2s, opacity 0.3s, width 0.4s; } .sfx-top-nav-btn:hover:not(:disabled) { background: rgba(255,255,255,0.12); color: #fff; } .sfx-top-nav-btn:disabled { opacity: 0.25; cursor: not-allowed; } #sfx-top-searchbar-wrap.sfx-collapsed .sfx-top-nav-btn { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } #sfx-top-search-close { background: none; border: none; color: rgba(255,255,255,0.45); font-size: 18px; line-height: 1; cursor: pointer; flex-shrink: 0; padding: 0 2px; transition: color 0.2s, opacity 0.3s, width 0.4s; } #sfx-top-search-close:hover { color: #fff; } #sfx-top-searchbar-wrap.sfx-collapsed #sfx-top-search-close { opacity: 0; pointer-events: none; width: 0; overflow: hidden; flex: none; padding: 0; margin: 0; } /* --- Capsule action buttons (settings + chat) --- */ .sfx-cap-sep { display: none; } .sfx-cap-action { background: none !important; border: none !important; outline: none !important; box-shadow: none !important; -webkit-appearance: none; color: rgba(255,255,255,0.38); width: 26px; height: 26px; border-radius: 0 !important; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; padding: 0; transition: color 0.18s ease; } .sfx-cap-action:hover, .sfx-cap-action:focus, .sfx-cap-action:active { background: none !important; border: none !important; outline: none !important; box-shadow: none !important; color: rgba(255,255,255,0.85); } .sfx-cap-action svg { width: 14px; height: 14px; } /* In expanded state: shrink separator+actions slightly so they don't dominate */ #sfx-top-searchbar-wrap:not(.sfx-collapsed) .sfx-cap-sep { height: 14px; } #kdrama-search-modal { position: fixed; bottom: 0; left: 0; width: 100%; z-index: 10004; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 -4px 15px rgba(0,0,0,0.3); padding: 15px 20px; box-sizing: border-box; display: flex; justify-content: center; align-items: center; opacity: 0; pointer-events: none; transition: opacity 0.2s ease-in-out; } #kdrama-search-modal.show { opacity: 1; pointer-events: auto; } .kdrama-search-controls { position: relative; width: calc(100% - 60px); max-width: 500px; display: flex; align-items: center; background-color: transparent; border-radius: 8px; } .kdrama-search-input-wrapper { position: relative; flex-grow: 1; display: flex; align-items: center; } #kdrama-search-input { width: 100%; padding: 12px 15px 12px 40px; border: none; border-radius: 8px; background-color: #333; color: white; font-size: 16px; outline: none; box-sizing: border-box; } #kdrama-search-input:focus { outline: 1px solid #0078D4; } #kdrama-search-input::placeholder { color: #bbb; } .kdrama-search-icon { position: absolute; left: 12px; color: #bbb; font-size: 18px; pointer-events: none; } .kdrama-search-nav-buttons { display: flex; align-items: center; margin-left: 10px; gap: 5px; } .kdrama-search-nav-button { background: none; border: none; color: white; width: 32px; height: 32px; border-radius: 6px; display: flex; align-items: center; justify-content: center; cursor: pointer; opacity: 0.7; transition: background-color 0.2s, opacity 0.2s; } .kdrama-search-nav-button:hover:not(:disabled) { background-color: rgba(255, 255, 255, 0.1); opacity: 1; } .kdrama-search-nav-button:disabled { opacity: 0.3; cursor: not-allowed; } #kdrama-search-close { background: none; border: none; color: white; font-size: 24px; cursor: pointer; margin-left: 15px; line-height: 1; opacity: 0.7; transition: opacity 0.2s; } #kdrama-search-close:hover { opacity: 1; } /* --- Highlight style --- */ .kdrama-highlight { background-color: #FFEB3B; color: black; font-weight: bold; padding: 2px 0; border-radius: 2px; } .kdrama-highlight.current { background-color: #4CAF50; color: white; box-shadow: 0 0 8px rgba(76, 175, 80, 0.8); } code .kdrama-highlight { background-color: #383131; color: #e0aeb4; } code .kdrama-highlight.current { background-color: #6a5e5e; color: #e0aeb4; } `); // --- Enhanced Drama Detection Patterns --- const dramaPatterns = [ // Pattern for lines with (reup) or (redo) prefix, broken by HTML tags. /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[/, // NEW: Pattern for drama names followed by mixed brackets like (AMZN...] with episode info /^(?:\(reup\)|\(redo\))?\s*([^([]+?)\s*[\[(][^\])]*[\])][\s\S]*?\((?:e?\d+(?:\s+of\s+\d+)?|\d+)ep\)\s*-/i, // NEW: Pattern for lines WITHOUT a prefix, broken by HTML tags. /^([a-zA-Z0-9][^[]*?)\s*\[/, // NEW: Pattern for drama names followed by "- coming soon" /^(?:\(reup\)|\(redo\))?\s*([^-]+?)\s*-\s*coming\s+soon/i, // NEW: Pattern for drama names followed by just "-" (nothing or whitespace after) /^(?:\(reup\)|\(redo\))?\s*([^-]+?)\s*-\s*$/, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\((?:e?\d+(?:\s+of\s+\d+)?|\d+)ep\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?(?:\s+S\d+)?)\s*\[.*?\]\s*\(\d+ep\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\(e\d+\s+of\s+\d+\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\(.*?ep.*?\)\s*-/i, /^(?:\(reup\)|\(redo\))?\s*([^[]+?)\s*\[.*?\]\s*\((?:e?\d+(?:\s+of\s+\d+)?|\d+)ep\)/i ]; // --- Helper function to extract drama name --- function extractDramaName(text) { const cleanText = text.trim(); if (cleanText.length < 10) return null; for (const pattern of dramaPatterns) { const match = cleanText.match(pattern); if (match && match[1]) { let dramaName = match[1].trim().replace(/:$/, '').trim().replace(/\s+/g, ' '); // Allow drama names as short as 1 character (like "M" or "DP") if (dramaName.length > 0 && dramaName.length < 200) return dramaName; } } return null; } // --- Helper function to open windows in the center of the screen --- function openInCenter(url, title) { const popWidth = 1000, popHeight = 700; // Get the actual screen dimensions const screenWidth = window.screen.availWidth || window.screen.width; const screenHeight = window.screen.availHeight || window.screen.height; // Calculate center position const left = Math.round((screenWidth / 2) - (popWidth / 2)); const top = Math.round((screenHeight / 2) - (popHeight / 2)); // Ensure the window doesn't go off-screen const finalLeft = Math.max(0, left); const finalTop = Math.max(0, top); const features = `width=${popWidth},height=${popHeight},top=${finalTop},left=${finalLeft},resizable=yes,scrollbars=yes,status=no,toolbar=no,menubar=no,location=no`; window.open(url, title, features); } // --- Helper function to show floating notifications --- function showNotification(message, type = 'info', duration = 3000) { // Remove any existing notification const existingNotification = document.querySelector('.kdrama-notification'); if (existingNotification) { existingNotification.remove(); } // Create new notification const notification = document.createElement('div'); notification.className = `kdrama-notification ${type}`; notification.textContent = message; document.body.appendChild(notification); // Trigger animation setTimeout(() => { notification.classList.add('show'); }, 10); // Auto-remove after specified duration setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { if (notification.parentNode) { notification.remove(); } }, 300); }, duration); } // --- Helper function to copy text to clipboard --- async function copyToClipboard(text) { try { if (navigator.clipboard && window.isSecureContext) { await navigator.clipboard.writeText(text); return true; } else { // Fallback for older browsers or non-secure contexts const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; textArea.style.top = '-999999px'; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const result = document.execCommand('copy'); document.body.removeChild(textArea); return result; } } catch (err) { console.error('Failed to copy text: ', err); return false; } } // --- Function to get MDL first result URL for copying --- async function getMdlFirstResultUrl(searchUrl, dramaName) { try { // Show loading indicator const circles = document.querySelectorAll('.mdl-circle'); circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = '0.5'; circle.style.cursor = 'wait'; } }); const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: searchUrl, timeout: 5000, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Cache-Control': 'no-cache' }, onload: function(response) { resolve(response); }, onerror: function(error) { reject(error); }, ontimeout: function() { reject(new Error('Request timed out')); } }); }); // Reset circle appearance circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = ''; circle.style.cursor = 'pointer'; } }); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } const htmlText = response.responseText; const resultMatch = htmlText.match(/]+id="mdl-\d+"[^>]*>[\s\S]*?]*class="[^"]*title[^"]*"[^>]*>[\s\S]*?]+href="([^"]+)"/); if (resultMatch) { return `https://mydramalist.com${resultMatch[1]}`; } else { // Fallback: try simpler regex patterns const simpleMatch = htmlText.match(/]+href="(\/\d+-[^"]+)"[^>]*>[^<]*<\/a>/); if (simpleMatch) { return `https://mydramalist.com${simpleMatch[1]}`; } } return null; } catch (error) { // Reset circle appearance const circles = document.querySelectorAll('.mdl-circle'); circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = ''; circle.style.cursor = 'pointer'; } }); console.error('Sinflix Modifier: Error getting MDL first result:', error); return null; } } // --- Function to load MDL page in background and open first result --- async function loadMdlPageAndOpenFirstResult(searchUrl, dramaName) { try { // Show loading indicator (update circle appearance) const circles = document.querySelectorAll('.mdl-circle'); circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = '0.5'; circle.style.cursor = 'wait'; } }); // Use GM_xmlhttpRequest with optimizations for speed const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: searchUrl, timeout: 5000, // Reduced timeout to 5 seconds headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Cache-Control': 'no-cache' }, onload: function(response) { resolve(response); }, onerror: function(error) { reject(error); }, ontimeout: function() { reject(new Error('Request timed out')); }, // Speed optimization: Only process first few KB of response onprogress: function(response) { // If we've received enough data and found what we need, we can stop if (response.responseText && response.responseText.length > 10000) { // Try to find the first result in the partial response const partialMatch = response.responseText.match(/]*class="[^"]*title[^"]*"[^>]*>[\s\S]*?]+href="([^"]+)"/); if (partialMatch) { // Found it early! Create a mock response const mockResponse = { status: 200, responseText: response.responseText, firstResultFound: partialMatch[1] }; resolve(mockResponse); return; } } } }); }); if (response.status !== 200) { throw new Error(`HTTP error! status: ${response.status}`); } let firstResultUrl = null; // Check if we found the result during onprogress if (response.firstResultFound) { firstResultUrl = `https://mydramalist.com${response.firstResultFound}`; } else { // Fast parsing using regex instead of DOMParser for better performance const htmlText = response.responseText; // Look for the first result using optimized regex const resultMatch = htmlText.match(/]+id="mdl-\d+"[^>]*>[\s\S]*?]*class="[^"]*title[^"]*"[^>]*>[\s\S]*?]+href="([^"]+)"/); if (resultMatch) { firstResultUrl = `https://mydramalist.com${resultMatch[1]}`; } else { // Fallback: try simpler regex patterns const simpleMatch = htmlText.match(/]+href="(\/\d+-[^"]+)"[^>]*>[^<]*<\/a>/); if (simpleMatch) { firstResultUrl = `https://mydramalist.com${simpleMatch[1]}`; } } } // Reset circle appearance circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = ''; circle.style.cursor = 'pointer'; } }); if (firstResultUrl) { // Open first result in popup if (config.linkOpenStyle === 'popup') { openInCenter(firstResultUrl, `sinflix_mdl_${dramaName}`); } else { window.open(firstResultUrl, '_blank'); } } else { // No results found, fallback to search page console.log('Sinflix Modifier: No results found on MDL, opening search page'); if (config.linkOpenStyle === 'popup') { openInCenter(searchUrl, 'sinflix_mdl_search'); } else { window.open(searchUrl, '_blank'); } } } catch (error) { console.error('Sinflix Modifier: Error loading MDL page:', error); // Reset circle appearance and fallback to search page const circles = document.querySelectorAll('.mdl-circle'); circles.forEach(circle => { if (circle.title.includes(dramaName)) { circle.style.opacity = ''; circle.style.cursor = 'pointer'; } }); // Fallback to opening search page if (config.linkOpenStyle === 'popup') { openInCenter(searchUrl, 'sinflix_mdl_search'); } else { window.open(searchUrl, '_blank'); } } } // --- Helper: convert a mega.nz URL to the bypass download URL --- function getMegaBypassUrl(megaUrl) { // Uses the wldbs workers API: base64-encode the URL, no extra fetch needed return `https://mega.wldbs.workers.dev/download?url=${btoa(megaUrl)}`; } // --- Helper: trigger background download without full page navigation --- function triggerDownload(url) { const a = document.createElement('a'); a.href = url; // Using download attribute with empty string hints the browser to save it a.download = ''; a.style.display = 'none'; document.body.appendChild(a); a.click(); // Small delay to ensure the browser processes the click before removal setTimeout(() => document.body.removeChild(a), 100); } // --- pst.moe Enhancements --- function enhancePstMoeContent() { if (!config.pstMoeEnhancements) return false; if (window.location.hostname !== 'pst.moe') return false; const preElement = document.querySelector('pre'); if (!preElement) return false; // Guard against double-processing if (preElement.dataset.sinflixProcessed) return true; preElement.dataset.sinflixProcessed = 'true'; const resolutionLinks = {}; const resolutionMegaLinks = {}; let currentResolution = null; // URL regex — matches http(s) URLs up to whitespace const linkRegex = /(https?:\/\/[^\s]+)/g; // --- Pre-scan: detect which resolution sections contain mega.nz links --- // This determines which header buttons to render before the DOM is built. const resolutionsWithMega = new Set(); const resolutionsWithLinks = new Set(); const resolutionMegaLinksMap = {}; { let scanRes = null; const scanLines = preElement.textContent.split('\n'); for (const scanLine of scanLines) { const rMatch = scanLine.trim().match(/^---\s+(.*?)\s+---/); if (rMatch) { scanRes = rMatch[1]; continue; } if (!scanRes) continue; const urlMatch = scanLine.match(/(https?:\/\/[^\s]+)/g); if (urlMatch) { resolutionsWithLinks.add(scanRes); const megaUrls = urlMatch.filter(u => u.includes('mega.nz/file/')); if (megaUrls.length > 0) { resolutionsWithMega.add(scanRes); if (!resolutionMegaLinksMap[scanRes]) resolutionMegaLinksMap[scanRes] = []; resolutionMegaLinksMap[scanRes].push(...megaUrls); } } } } /** * Process a plain-text string and return a DocumentFragment containing: * - resolution-header elements (with Copy buttons) for "--- ... ---" lines * - elements for URLs (+ optional mega bypass circles) * - plain Text nodes for everything else * Preserves all original whitespace / newlines. */ function processTextContent(text) { const fragment = document.createDocumentFragment(); const lines = text.split('\n'); lines.forEach((line, lineIdx) => { // --- Resolution header line --- const resMatch = line.trim().match(/^---\s+(.*?)\s+---/); if (resMatch) { currentResolution = resMatch[1]; if (!resolutionLinks[currentResolution]) resolutionLinks[currentResolution] = []; if (!resolutionMegaLinks[currentResolution]) resolutionMegaLinks[currentResolution] = []; const resSpan = document.createElement('span'); resSpan.className = 'sinflix-res-header'; const textNode = document.createTextNode(line.trim() + ' '); resSpan.appendChild(textNode); // Show mega.nz copy buttons when section has mega links if (resolutionsWithMega.has(currentResolution)) { const copyBtn = document.createElement('button'); copyBtn.className = 'sinflix-copy-btn'; copyBtn.dataset.res = currentResolution; copyBtn.textContent = 'Copy All Links'; resSpan.appendChild(copyBtn); if (config.megaBypass) { resSpan.appendChild(document.createTextNode(' ')); const bypassBtn = document.createElement('button'); bypassBtn.className = 'sinflix-copy-bypass-btn'; bypassBtn.dataset.bypassRes = currentResolution; bypassBtn.textContent = 'Copy Bypass Links'; resSpan.appendChild(bypassBtn); } } fragment.appendChild(resSpan); fragment.appendChild(document.createTextNode('\n')); return; } // --- Normal line: split on URLs --- linkRegex.lastIndex = 0; let lastIndex = 0; let match; while ((match = linkRegex.exec(line)) !== null) { // Text before the URL if (match.index > lastIndex) { fragment.appendChild(document.createTextNode(line.slice(lastIndex, match.index))); } const rawUrl = match[0]; const cleanUrl = rawUrl.replace(/"/g, '%22'); // Track links per resolution if (currentResolution) { resolutionLinks[currentResolution].push(cleanUrl); if (cleanUrl.includes('mega.nz/file/')) { resolutionMegaLinks[currentResolution].push(cleanUrl); } } // Create element const anchor = document.createElement('a'); anchor.href = cleanUrl; anchor.target = '_blank'; anchor.rel = 'noopener noreferrer'; anchor.textContent = rawUrl; fragment.appendChild(anchor); // Append bypass circle for mega links if (config.megaBypass && cleanUrl.includes('mega.nz/file/')) { const circle = document.createElement('span'); circle.className = 'sinflix-mega-circle'; circle.title = 'Bypass & download via mega.wldbs.workers.dev'; circle.dataset.megaUrl = cleanUrl; fragment.appendChild(circle); } // Append download circle for FileDitch links if (config.showFdCircle && cleanUrl.includes('fileditchfiles.me')) { const dlCircle = document.createElement('span'); dlCircle.className = 'sinflix-fd-dl-circle'; dlCircle.title = '\u2b07 Download via FileDitch (auto-clicks download & closes)'; dlCircle.dataset.fdUrl = cleanUrl; fragment.appendChild(dlCircle); } lastIndex = match.index + match[0].length; } // Remaining text after last URL (or the whole line if no URL) if (lastIndex < line.length) { fragment.appendChild(document.createTextNode(line.slice(lastIndex))); } // Re-add the newline that split() removed (skip after very last line) if (lineIdx < lines.length - 1) { fragment.appendChild(document.createTextNode('\n')); } }); return fragment; } // Walk child nodes of
, replacing only Text nodes.
        // Element nodes (Pygments  etc.) are left untouched.
        const childNodes = Array.from(preElement.childNodes);
        for (const node of childNodes) {
            if (node.nodeType === Node.TEXT_NODE) {
                const fragment = processTextContent(node.textContent);
                preElement.replaceChild(fragment, node);
            }
            // Element nodes: leave as-is to preserve syntax highlighting
        }

        if (config.showFdCircle) {
            document.querySelectorAll('.sinflix-fd-dl-circle').forEach(circle => {
                circle.addEventListener('click', () => {
                    if (circle.classList.contains('fd-loading')) return;
                    circle.classList.add('fd-loading');
                    const w = window.open(circle.dataset.fdUrl + '#sfx=dl', '_blank',
                        'width=900,height=650,menubar=no,toolbar=no,status=no,location=yes');
                    showNotification('Opening FileDitch\u2026 will auto-download & close.', 'info', 5000);
                    setTimeout(() => circle.classList.remove('fd-loading'), 18000);
                    if (!w) showNotification('Popup blocked! Allow popups for pst.moe.', 'error', 6000);
                });
            });
        }

        document.querySelectorAll('.sinflix-copy-btn').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const res = e.target.getAttribute('data-res');
                // Copy only the mega.nz links from this section
                const megaLinks = resolutionMegaLinksMap[res] || [];
                if (megaLinks.length > 0) {
                    showNotification('Copying links...', 'info');
                    navigator.clipboard.writeText(megaLinks.join('\n')).then(() => {
                        showNotification(`${megaLinks.length} link(s) copied!`, 'success');
                        const oldText = e.target.innerText;
                        e.target.innerText = 'Copied!';
                        setTimeout(() => { e.target.innerText = oldText; }, 2000);
                    }).catch(() => {
                        showNotification('Failed to copy links!', 'error');
                    });
                }
            });
        });

        // --- Copy Bypass Links buttons ---
        if (config.megaBypass) {
            document.querySelectorAll('.sinflix-copy-bypass-btn').forEach(btn => {
                btn.addEventListener('click', (e) => {
                    const res = e.target.getAttribute('data-bypass-res');
                    const megaLinks = resolutionMegaLinksMap[res] || [];
                    if (megaLinks.length === 0) {
                        showNotification('No Mega.nz links found in this section.', 'error');
                        return;
                    }
                    const bypassLinks = megaLinks.map(url => getMegaBypassUrl(url));
                    const textToCopy = bypassLinks.join('\n');
                    showNotification('Copying bypass links...', 'info');
                    navigator.clipboard.writeText(textToCopy).then(() => {
                        showNotification(`${bypassLinks.length} link(s) copied!`, 'success');
                        const oldText = e.target.innerText;
                        e.target.innerText = 'Copied!';
                        setTimeout(() => { e.target.innerText = oldText; }, 2000);
                    }).catch(() => {
                        showNotification('Failed to copy links!', 'error');
                    });
                });
            });

            // --- Mega bypass: grey circle click → trigger bypass download ---
            document.querySelectorAll('.sinflix-mega-circle').forEach(circle => {
                circle.addEventListener('click', (e) => {
                    e.stopPropagation();
                    const megaUrl = circle.getAttribute('data-mega-url');
                    if (!megaUrl) return;
                    const bypassUrl = getMegaBypassUrl(megaUrl);
                    showNotification('Download started…', 'success', 2000);
                    triggerDownload(bypassUrl);
                });
            });
        }

        return true;
    }

    // --- Buzzheavier Enhancements ---
    function enhanceBuzzheavierContent() {
        if (!config.buzzheavierEnhancements) return false;
        if (!window.location.hostname.includes('buzzheavier.com')) return false;

        const isHomePage = window.location.pathname.length > 1 && !window.location.pathname.endsWith('/download') && document.querySelector('#tbody');
        const isSinglePage = document.querySelector('#preview') && document.querySelector('a[href$="/download"]');

        const ICONS = {
            play: '',
            copy: '',
            downloadSimple: '',
            check: '',
            loading: ''
        };



        const fetchDirectLink = (url, callback) => {
            const domain = new URL(url).origin;
            const downloadUrl = url.replace(/\/$/, '') + '/download';
            GM_xmlhttpRequest({
                method: "HEAD",
                url: downloadUrl,
                headers: {
                    "hx-current-url": url,
                    "hx-request": "true",
                    "referer": url
                },
                onload: function(response) {
                    let redirectPath = null;
                    const headers = response.responseHeaders;
                    const headerMatch = headers.match(/hx-redirect:\s*(.*)/i);
                    if (headerMatch && headerMatch[1]) {
                        redirectPath = headerMatch[1].trim();
                    }
                    if (redirectPath) {
                        let finalUrl = redirectPath.startsWith('http') ? redirectPath : domain + redirectPath;
                        callback(finalUrl);
                    } else {
                        showNotification("Failed to obtain direct link.", "error");
                        callback(null);
                    }
                },
                onerror: function(err) {
                    showNotification("Network error obtaining link.", "error");
                    callback(null);
                }
            });
        };

        const handleAction = (type, pageUrl, btnElement) => {
            if (btnElement.classList.contains('loading')) return;
            const originalIcon = btnElement.innerHTML;
            btnElement.innerHTML = ICONS.loading;
            btnElement.classList.add('loading');

            fetchDirectLink(pageUrl, (directUrl) => {
                btnElement.classList.remove('loading');
                if (!directUrl) {
                    btnElement.innerHTML = originalIcon;
                    return;
                }
                if (type === 'copy') {
                    navigator.clipboard.writeText(directUrl).then(() => {
                        btnElement.innerHTML = ICONS.check;
                        setTimeout(() => { btnElement.innerHTML = originalIcon; }, 2000);
                    });
                } else if (type === 'download') {
                    btnElement.innerHTML = originalIcon;
                    window.location.assign(directUrl);
                }
            });
        };

        const createActionBtn = (icon, title, type, fileUrl, extraClass) => {
            const btn = document.createElement('button');
            btn.className = `bh-btn ${extraClass || ''}`;
            btn.title = title;
            btn.innerHTML = icon;
            btn.onclick = (e) => {
                e.preventDefault();
                e.stopPropagation();
                handleAction(type, fileUrl, btn);
            };
            return btn;
        };

        const copyLinks = (linksArray, btnElement) => {
            if (!linksArray || linksArray.length === 0) return;
            showNotification('Copying links... Please wait!', 'info');

            let directLinks = [];
            let processed = 0;
            let total = linksArray.length;

            linksArray.forEach(pageUrl => {
                fetchDirectLink(pageUrl, (directUrl) => {
                    processed++;
                    if (directUrl) {
                        directLinks.push(directUrl);
                    }
                    if (processed === total) {
                        if (directLinks.length > 0) {
                            navigator.clipboard.writeText(directLinks.join('\n')).then(() => {
                                showNotification('All direct links copied successfully!', 'success');
                                const oldText = btnElement.innerText;
                                btnElement.innerText = 'Copied!';
                                setTimeout(() => btnElement.innerText = oldText, 2000);
                            }).catch(() => {
                                showNotification('Failed to copy links!', 'error');
                            });
                        } else {
                            showNotification('No direct links found!', 'error');
                        }
                    }
                });
            });
        };

        if (isHomePage) {
            const tbody = document.querySelector('#tbody');
            if (tbody && config.buzzSplitQuality) {
                const rows = Array.from(tbody.querySelectorAll('tr.editable'));
                const qualities = { '1080p': [], '720p': [], '540p': [], '480p': [], 'Other': [] };

                rows.forEach(row => {
                    const linkEl = row.querySelector('a[href^="/"]');
                    if (!linkEl) return;

                    const name = linkEl.innerText.toLowerCase();
                    let matched = false;
                    for (const q of Object.keys(qualities)) {
                        if (q !== 'Other' && name.includes(q)) {
                            qualities[q].push(row);
                            matched = true;
                            break;
                        }
                    }
                    if (!matched) qualities['Other'].push(row);
                });

                const parentTable = tbody.closest('table');
                const container = parentTable.parentNode;
                const headerRow = parentTable.querySelector('thead').outerHTML;
                const tableClass = parentTable.className;

                parentTable.style.display = 'none';

                Object.keys(qualities).forEach(key => {
                    if (qualities[key].length > 0) {
                        const wrapper = document.createElement('div');
                        wrapper.className = 'w-full relative shadow overflow-hidden sm:rounded-lg overflow-x-auto my-6';

                        let copyAllBtnHtml = '';
                        if (config.buzzCopyLinks) {
                            copyAllBtnHtml = ``;
                        }

                        wrapper.innerHTML = `
                            

${key} ${copyAllBtnHtml}

${headerRow}
`; container.insertBefore(wrapper, parentTable); const newTbody = wrapper.querySelector(`#tbody-${key}`); const linksForQuality = []; qualities[key].forEach(row => { newTbody.appendChild(row); const linkEl = row.querySelector('a[href^="/"]'); if (!linkEl) return; const fileUrl = linkEl.href; linksForQuality.push(fileUrl); if (!row.querySelector('.bh-actions')) { const actionsContainer = document.createElement('div'); actionsContainer.className = 'bh-actions'; if (config.buzzCopyLinks) { actionsContainer.appendChild(createActionBtn(ICONS.copy, 'Copy Direct Link', 'copy', fileUrl, 'copy-btn')); } if (config.buzzDirectDownload) { actionsContainer.appendChild(createActionBtn(ICONS.downloadSimple, 'Direct Download', 'download', fileUrl, 'dl-btn')); } linkEl.parentNode.appendChild(actionsContainer); } }); const copyBtn = wrapper.querySelector(`.sinflix-copy-btn[data-q="${key}"]`); if (copyBtn) { copyBtn.addEventListener('click', function() { copyLinks(linksForQuality, this); }); } } }); } else if (tbody) { const rows = Array.from(tbody.querySelectorAll('tr.editable')); const allLinks = []; rows.forEach(row => { const linkEl = row.querySelector('a[href^="/"]'); if (!linkEl) return; const fileUrl = linkEl.href; allLinks.push(fileUrl); if (!row.querySelector('.bh-actions')) { const actionsContainer = document.createElement('div'); actionsContainer.className = 'bh-actions'; if (config.buzzCopyLinks) { actionsContainer.appendChild(createActionBtn(ICONS.copy, 'Copy Direct Link', 'copy', fileUrl, 'copy-btn')); } if (config.buzzDirectDownload) { actionsContainer.appendChild(createActionBtn(ICONS.downloadSimple, 'Direct Download', 'download', fileUrl, 'dl-btn')); } linkEl.parentNode.appendChild(actionsContainer); } }); if (config.buzzCopyLinks) { const parentTable = tbody.closest('table'); const copyBtn = document.createElement('button'); copyBtn.className = 'sinflix-copy-btn btn btn-sm bg-blue-600 text-white px-3 py-1 rounded my-2'; copyBtn.innerText = 'Copy All Links'; copyBtn.onclick = function() { copyLinks(allLinks, this); }; parentTable.parentNode.insertBefore(copyBtn, parentTable); } } } else if (isSinglePage) { const dlBtn = document.querySelector('a.gay-button') || document.querySelector('a[href$="/download"]'); if (dlBtn && !document.querySelector('.bh-actions.single-page')) { const fileUrl = window.location.href; const container = document.createElement('div'); container.className = 'bh-actions single-page'; if (config.buzzCopyLinks) { container.appendChild(createActionBtn(ICONS.copy, 'Copy Direct Link', 'copy', fileUrl, 'copy-btn')); } if (dlBtn.parentNode) { dlBtn.parentNode.insertBefore(container, dlBtn.nextSibling); } } } return true; } // --- Main Processing Function --- function enhancePageContent() { const content = document.querySelector('.entry-text article'); if (!content) { console.log('Sinflix Modifier: Content not found, retrying...'); return false; } const currentVersion = 'v6.4.1_selective_popup'; if (content.dataset.enhancedv === currentVersion) { console.log(`Sinflix Modifier: Content already enhanced (${currentVersion}). Skipping.`); return true; } console.log(`Sinflix Modifier (${currentVersion}): Processing...`); content.dataset.enhancedv = currentVersion; // NEW: Reorder sections FIRST, before any other processing if (config.moveCurrentlyAiringToTop) { reorderSections(); } const buzzRegex = /\b(?![a-zA-Z]{12}\b)([a-zA-Z0-9]{12})\b/g; if (config.showGoogleCircle || config.showMdlCircle) { document.querySelectorAll('.kdrama-circle-container').forEach(el => el.remove()); const textNodes = []; const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, { acceptNode: n => (!n.parentNode.closest('a, .kdrama-highlight, .kdrama-search-icon, .kdrama-circle-container') && n.nodeValue.trim().length > 0) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }); let node; while (node = walker.nextNode()) textNodes.push(node); textNodes.forEach(textNode => { let fullText = textNode.textContent.replace(/=\r?\n/g, '').replace(/=([0-9A-Fa-f]{2})/g, (m, p1) => { try { return String.fromCharCode(parseInt(p1, 16)); } catch(e) { return m; } }); const lines = fullText.split('\n'); let fragment = document.createDocumentFragment(); let lastOffset = 0; let processedAnyLine = false; lines.forEach(line => { const dramaName = extractDramaName(line); if (dramaName) { const lineStartIndex = fullText.indexOf(line, lastOffset); if (lineStartIndex > lastOffset) fragment.appendChild(document.createTextNode(fullText.substring(lastOffset, lineStartIndex))); const container = document.createElement('span'); container.className = 'kdrama-circle-container'; if (config.showGoogleCircle) { const searchQuery = `${dramaName} ${config.googleSearchSuffix}`.trim(); const googleUrl = `https://www.google.com/search?q=${encodeURIComponent(searchQuery)}`; const googleCircle = document.createElement('span'); googleCircle.className = 'kdrama-circle google-circle'; googleCircle.title = `Search '${dramaName}' on Google`; googleCircle.onclick = (e) => { e.stopPropagation(); // USE SETTING: Check which style the user prefers if (config.linkOpenStyle === 'popup') { openInCenter(googleUrl, 'sinflix_Google Search'); } else { window.open(googleUrl, '_blank'); } }; container.appendChild(googleCircle); } if (config.showMdlCircle) { const mdlUrl = `https://mydramalist.com/search?q=${encodeURIComponent(dramaName)}&adv=titles&ty=68&co=3&so=relevance`; const mdlCircle = document.createElement('span'); mdlCircle.className = 'kdrama-circle mdl-circle'; mdlCircle.title = `Search '${dramaName}' on MyDramaList\nCtrl+Click to copy first result link`; mdlCircle.onclick = async (e) => { e.stopPropagation(); // Check if Ctrl key is pressed if (e.ctrlKey) { showNotification('Getting the link...', 'info'); const firstResultUrl = await getMdlFirstResultUrl(mdlUrl, dramaName); if (firstResultUrl) { const success = await copyToClipboard(firstResultUrl); if (success) { showNotification('Link copied to clipboard!', 'success'); } else { showNotification('Failed to copy link to clipboard', 'error'); } } else { showNotification('No results found for this drama', 'error'); } return; } // Normal click behavior - Load page in background and get first result loadMdlPageAndOpenFirstResult(mdlUrl, dramaName); }; container.appendChild(mdlCircle); } fragment.appendChild(container); fragment.appendChild(document.createTextNode(line)); processedAnyLine = true; } else { const lineStartIndex = fullText.indexOf(line, lastOffset); if (lineStartIndex > lastOffset) fragment.appendChild(document.createTextNode(fullText.substring(lastOffset, lineStartIndex))); fragment.appendChild(document.createTextNode(line)); } fragment.appendChild(document.createTextNode('\n')); lastOffset = fullText.indexOf(line, lastOffset) + line.length + 1; }); if (lastOffset < fullText.length) fragment.appendChild(document.createTextNode(fullText.substring(lastOffset))); if (processedAnyLine && textNode.parentNode) textNode.parentNode.replaceChild(fragment, textNode); }); } if (config.convertBuzzheavierLinks) { const linkWalker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, { acceptNode: n => n.parentNode.nodeName !== 'A' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }); const linkNodes = []; let n; while (n = linkWalker.nextNode()) { buzzRegex.lastIndex = 0; if (buzzRegex.test(n.textContent)) linkNodes.push(n); } linkNodes.forEach(node => { if (!document.body.contains(node) || node.parentNode.nodeName === 'A') return; const fragment = document.createDocumentFragment(); const text = node.textContent; let lastIndex = 0; buzzRegex.lastIndex = 0; let match; while ((match = buzzRegex.exec(text)) !== null) { if (match.index > lastIndex) fragment.appendChild(document.createTextNode(text.slice(lastIndex, match.index))); const link = document.createElement('a'); link.href = `https://buzzheavier.com/${match[1]}`; link.textContent = link.href; link.target = '_blank'; fragment.appendChild(link); lastIndex = match.index + match[0].length; } if (lastIndex < text.length) fragment.appendChild(document.createTextNode(text.slice(lastIndex))); if (fragment.hasChildNodes() && node.parentNode) node.parentNode.replaceChild(fragment, node); }); } // NEW: Process existing links to apply special behavior only to specific links const existingLinks = content.querySelectorAll('a[href]'); existingLinks.forEach(link => { // Skip links that are already processed or are internal links if (link.dataset.sinflixProcessed || link.href.startsWith('#') || link.href.startsWith('javascript:')) return; link.dataset.sinflixProcessed = 'true'; // Special handling for SinFlix chat box link if (link.href.includes('my.cbox.ws/sin-flix')) { // Remove existing target and click handlers link.removeAttribute('target'); // Add new click handler based on chat box preference link.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); openInCenter(link.href, 'sinflix_chat'); }); } // For all other links, leave them as they are (normal behavior) }); console.log('Sinflix Modifier: Processing complete. ✨'); // Mark content as processed to show all headers content.classList.add('sinflix-processed'); return true; } // --- Immediate early reordering function --- function tryEarlyReorder() { if (!config.moveCurrentlyAiringToTop) return; const content = document.querySelector('.entry-text article'); if (content && content.dataset.sectionsReordered !== 'true') { reorderSections(); } } // --- Section Reordering Function --- function reorderSections() { const content = document.querySelector('.entry-text article'); if (!content) { console.log('Sinflix Modifier: Content not found for reordering'); return; } // Check if already reordered to avoid duplicate processing if (content.dataset.sectionsReordered === 'true') { return; } try { // Find all h4 elements that represent section headers const headersNodeList = content.querySelectorAll('h4'); const headers = Array.from(headersNodeList); let currentlyAiringHeader = null; let insertBeforeHeader = null; // Find the "Currently Airing" header for (const header of headers) { if (header.textContent.trim().toLowerCase().includes('currently airing')) { currentlyAiringHeader = header; break; } } if (!currentlyAiringHeader) { console.log('Sinflix Modifier: "Currently Airing" section not found'); headers.forEach(h => h.classList.add('sinflix-visible')); content.dataset.sectionsReordered = 'true'; return; } // Find the insertion point dynamically: // The first h4 that is NOT the page-title header (index 0) and NOT Currently Airing. // This works regardless of what the second section is named. const firstH4 = headers[0]; for (const header of headers) { if (header === firstH4) continue; // skip page title if (header === currentlyAiringHeader) continue; // skip itself insertBeforeHeader = header; break; } if (!insertBeforeHeader) { console.log('Sinflix Modifier: No valid insertion point found for reordering'); headers.forEach(h => h.classList.add('sinflix-visible')); content.dataset.sectionsReordered = 'true'; return; } // Check if Currently Airing is already before the insertion point const airingIndex = headers.indexOf(currentlyAiringHeader); const insertIndex = headers.indexOf(insertBeforeHeader); if (airingIndex < insertIndex) { console.log('Sinflix Modifier: Currently Airing is already at the top'); content.dataset.sectionsReordered = 'true'; headers.forEach(h => h.classList.add('sinflix-visible')); return; } // Collect all content belonging to the "Currently Airing" section // (everything between the h4 and the next h4) const currentlyAiringContent = []; let currentElement = currentlyAiringHeader.nextElementSibling; while (currentElement && currentElement.tagName !== 'H4') { currentlyAiringContent.push(currentElement); currentElement = currentElement.nextElementSibling; } // Remove "Currently Airing" section from its current position currentlyAiringHeader.remove(); currentlyAiringContent.forEach(el => el.remove()); // Re-insert before the target section const parentElement = insertBeforeHeader.parentNode; parentElement.insertBefore(currentlyAiringHeader, insertBeforeHeader); let insertAfter = currentlyAiringHeader; currentlyAiringContent.forEach(el => { parentElement.insertBefore(el, insertAfter.nextSibling); insertAfter = el; }); // Add a visual separator after the moved section if one doesn't already exist if (insertAfter.nextSibling && insertAfter.nextSibling.tagName !== 'HR') { const separator = document.createElement('hr'); parentElement.insertBefore(separator, insertAfter.nextSibling); } // Mark as reordered to prevent duplicate processing content.dataset.sectionsReordered = 'true'; // Reveal all headers now that reordering is complete content.querySelectorAll('h4').forEach(h => h.classList.add('sinflix-visible')); console.log('Sinflix Modifier: Successfully moved "Currently Airing" section to top'); } catch (error) { console.error('Sinflix Modifier: Error reordering sections:', error); // Show all headers in case of error content.querySelectorAll('h4').forEach(h => h.classList.add('sinflix-visible')); } } // --- Settings UI --- function createSettingsUI() { const settingsButton = document.createElement('div'); settingsButton.id = 'kdrama-settings-button'; settingsButton.innerHTML = ``; // Only show the fixed settings button when the top search capsule is OFF if (!config.showTopSearchBar) { document.body.appendChild(settingsButton); } const modal = document.createElement('div'); modal.id = 'kdrama-settings-modal'; modal.innerHTML = `

SinFlix Modifier Settings

Features

Google Search Icons
Show blue circle icons for Google search next to drama titles
Google Search Keyword Suffix
Appended to title when searching (e.g. "TV Series")
MyDramaList Search Icons
Show purple circle icons for MyDramaList search next to drama titles
BuzzHeavier Link Conversion
Automatically convert BuzzHeavier IDs to clickable links

Interface

Back to Top Button
Show floating button to quickly scroll to top of page
SinFlix Chat Button
Show floating button for quick access to SinFlix community chat
Move "Currently Airing" to Top
Automatically move the Currently Airing section to the top of the page
Top Search Bar
Show a Dynamic Island-style search bar at the top. Morphs into a circle icon after 5s or on scroll

pst.moe

Enable pst.moe Enhancements
Convert links into hyperlinks and add "Copy All Links" option
Mega.nz Link Bypass
Add grey ● circle next to each Mega link to bypass & download instantly. Also adds "Copy Links" per section.
${'showFdCircle' in config ? `
FileDitch Download Circles
Show an orange ● circle next to each fileditchfiles.me link. Clicking opens the page, auto-clicks the download button, and closes the window.
` : '
'}

Mega.nz

Bypass Download Button
Show a floating button on mega.nz file pages to download via bypass instantly

Buzzheavier

Enable Buzzheavier Features
Master switch for Buzzheavier enhancements
Split by Quality
Divide folder table into multiple tables based on resolution
Direct Download Links
Add direct download button next to file links
Copy Links Button
Add button to copy all download links from table/page

Link Behavior

Search Circle Icons Opening Style
Chat Box Opening Style
`; document.body.appendChild(modal); // NEW: Set the correct radio button based on saved config document.querySelector(`input[name="linkStyle"][value="${config.linkOpenStyle}"]`).checked = true; document.querySelector(`input[name="chatBoxStyle"][value="${config.chatBoxOpenStyle}"]`).checked = true; const saveButton = document.getElementById('kdrama-save-button'); const closeBtn = document.getElementById('kdrama-settings-close'); settingsButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); modal.style.display = 'flex'; // Add a small delay for the animation setTimeout(() => modal.classList.add('show'), 10); }); modal.addEventListener('click', (e) => { if (e.target === modal) { modal.classList.remove('show'); setTimeout(() => modal.style.display = 'none', 200); } }); closeBtn.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); modal.classList.remove('show'); setTimeout(() => modal.style.display = 'none', 200); }); // Additional escape key handler for settings modal document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && modal.style.display === 'flex') { modal.classList.remove('show'); setTimeout(() => modal.style.display = 'none', 200); e.preventDefault(); } }); saveButton.addEventListener('click', (e) => { e.preventDefault(); // Save checkbox values GM_setValue('showGoogleCircle', document.getElementById('setting-google').checked); GM_setValue('showMdlCircle', document.getElementById('setting-mdl').checked); GM_setValue('convertBuzzheavierLinks', document.getElementById('setting-buzz').checked); GM_setValue('showBackToTopButton', document.getElementById('setting-back-to-top').checked); GM_setValue('moveCurrentlyAiringToTop', document.getElementById('setting-move-airing').checked); GM_setValue('showChatBoxButton', document.getElementById('setting-chat-box').checked); GM_setValue('pstMoeEnhancements', document.getElementById('setting-pstmoe').checked); GM_setValue('megaBypass', document.getElementById('setting-mega-bypass').checked); GM_setValue('megaNzButton', document.getElementById('setting-mega-nz-btn').checked); const fdEl = document.getElementById('setting-fd-resolve'); if (fdEl) GM_setValue('showFdCircle', fdEl.checked); // NEW: Save the selected radio button values const selectedStyle = document.querySelector('input[name="linkStyle"]:checked').value; GM_setValue('linkOpenStyle', selectedStyle); const selectedChatBoxStyle = document.querySelector('input[name="chatBoxStyle"]:checked').value; GM_setValue('chatBoxOpenStyle', selectedChatBoxStyle); GM_setValue('googleSearchSuffix', document.getElementById('setting-google-suffix').value); // NEW: Buzzheavier Save GM_setValue('buzzheavierEnhancements', document.getElementById('setting-buzz-main').checked); GM_setValue('buzzSplitQuality', document.getElementById('setting-buzz-split').checked); GM_setValue('buzzDirectDownload', document.getElementById('setting-buzz-dl').checked); GM_setValue('buzzCopyLinks', document.getElementById('setting-buzz-copy').checked); const customSchemeEl = document.getElementById('setting-buzz-custom-scheme'); if (customSchemeEl) GM_setValue('buzzCustomScheme', customSchemeEl.value); // NEW: Top search bar toggle GM_setValue('showTopSearchBar', document.getElementById('setting-top-searchbar').checked); location.reload(); }); } // --- Floating Action Buttons (Back to Top & Search) --- let highlightedElements = []; let currentMatchIndex = -1; function createFloatingButtons() { const backToTopBtn = document.createElement('button'); backToTopBtn.id = 'kdrama-back-to-top'; backToTopBtn.className = 'kdrama-float-button'; backToTopBtn.setAttribute('aria-label', 'Back to top'); backToTopBtn.title = 'Back to top'; backToTopBtn.innerHTML = ``; document.body.appendChild(backToTopBtn); // Chat button: only show as floating button when top search capsule is OFF let chatBtn = null; if (config.showChatBoxButton && !config.showTopSearchBar) { chatBtn = document.createElement('button'); chatBtn.id = 'kdrama-chat-button'; chatBtn.className = 'kdrama-float-button show'; chatBtn.setAttribute('aria-label', 'Open SinFlix Chat'); chatBtn.title = 'SinFlix Chat'; chatBtn.innerHTML = ``; document.body.appendChild(chatBtn); } function updateButtonPositions() { const baseBottom = 30; const buttonSpacing = 54; // Position chat button if enabled if (config.showChatBoxButton && chatBtn) { chatBtn.style.bottom = `${baseBottom}px`; } // Position back-to-top button at the top of the stack (it will handle its own visibility) const topPosition = config.showChatBoxButton ? baseBottom + buttonSpacing : baseBottom; backToTopBtn.style.bottom = `${topPosition}px`; } function updateButtonPositionsWithAnimation() { const baseBottom = 30; const buttonSpacing = 54; let currentBottom = baseBottom; // Calculate positions based on visible buttons const isBackToTopVisible = backToTopBtn.classList.contains('show'); if (isBackToTopVisible) { // Back-to-top is visible, so other buttons move up backToTopBtn.style.bottom = `${currentBottom}px`; currentBottom += buttonSpacing; } // Chat button position (if enabled) if (config.showChatBoxButton && chatBtn) { chatBtn.style.bottom = `${currentBottom}px`; } } updateButtonPositions(); window.addEventListener('scroll', () => { const scrollThreshold = 200; const shouldShowBackToTop = window.scrollY > scrollThreshold && config.showBackToTopButton; const isCurrentlyShowing = backToTopBtn.classList.contains('show'); if (shouldShowBackToTop && !isCurrentlyShowing) { // Show back-to-top button and animate other buttons backToTopBtn.classList.add('show'); updateButtonPositionsWithAnimation(); } else if (!shouldShowBackToTop && isCurrentlyShowing) { // Hide back-to-top button and animate other buttons backToTopBtn.classList.remove('show'); updateButtonPositionsWithAnimation(); } }); backToTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); // NEW: Add chat button click handler if (config.showChatBoxButton && chatBtn) { chatBtn.addEventListener('click', () => { const chatUrl = 'https://my.cbox.ws/sin-flix'; openInCenter(chatUrl, 'sinflix_chat'); }); } } // --- Top Search Bar (Dynamic Island) --- function createTopSearchBar() { if (!config.showTopSearchBar) return; // The wrapper pill/circle const wrap = document.createElement('div'); wrap.id = 'sfx-top-searchbar-wrap'; // Search icon const iconEl = document.createElement('span'); iconEl.id = 'sfx-top-search-icon'; iconEl.innerHTML = ``; wrap.appendChild(iconEl); // Collapsed label (hidden when expanded, visible when collapsed) const labelEl = document.createElement('span'); labelEl.id = 'sfx-top-search-label'; labelEl.textContent = 'Search'; wrap.appendChild(labelEl); // Text input const input = document.createElement('input'); input.id = 'sfx-top-search-input'; input.type = 'text'; input.placeholder = 'Search on page…'; input.setAttribute('autocomplete', 'off'); input.setAttribute('spellcheck', 'false'); wrap.appendChild(input); // Match counter const counter = document.createElement('span'); counter.id = 'sfx-top-search-count'; wrap.appendChild(counter); // Prev / Next buttons const prevBtn = document.createElement('button'); prevBtn.className = 'sfx-top-nav-btn'; prevBtn.title = 'Previous match'; prevBtn.disabled = true; prevBtn.innerHTML = ``; wrap.appendChild(prevBtn); const nextBtn = document.createElement('button'); nextBtn.className = 'sfx-top-nav-btn'; nextBtn.title = 'Next match'; nextBtn.disabled = true; nextBtn.innerHTML = ``; wrap.appendChild(nextBtn); // Close / collapse button const closeBtn = document.createElement('button'); closeBtn.id = 'sfx-top-search-close'; closeBtn.title = 'Dismiss search bar'; closeBtn.innerHTML = '×'; wrap.appendChild(closeBtn); // Separator between search controls and utility icons const capSep = document.createElement('span'); capSep.className = 'sfx-cap-sep'; wrap.appendChild(capSep); // Settings icon button (inside capsule) const capSettings = document.createElement('button'); capSettings.className = 'sfx-cap-action'; capSettings.title = 'Settings'; capSettings.innerHTML = ``; capSettings.addEventListener('click', (e) => { e.stopPropagation(); const modal = document.getElementById('kdrama-settings-modal'); if (modal) { modal.style.display = 'flex'; setTimeout(() => modal.classList.add('show'), 10); } }); wrap.appendChild(capSettings); // Chat icon button (inside capsule, only if enabled) if (config.showChatBoxButton) { const capChat = document.createElement('button'); capChat.className = 'sfx-cap-action'; capChat.title = 'SinFlix Chat'; capChat.innerHTML = ``; capChat.addEventListener('click', (e) => { e.stopPropagation(); const chatUrl = 'https://my.cbox.ws/sin-flix'; openInCenter(chatUrl, 'sinflix_chat'); }); wrap.appendChild(capChat); } document.body.appendChild(wrap); // ---- State: start collapsed ---- let isCollapsed = true; wrap.classList.add('sfx-collapsed'); let sfxHighlights = []; let sfxCurrentIdx = -1; // ---- Collapse / expand helpers ---- function collapse() { if (isCollapsed) return; isCollapsed = true; wrap.classList.add('sfx-collapsed'); input.blur(); } function expand() { if (!isCollapsed) return; isCollapsed = false; wrap.classList.remove('sfx-collapsed'); // Small bounce feedback wrap.classList.add('sfx-expanding'); setTimeout(() => wrap.classList.remove('sfx-expanding'), 600); // Focus input after animation settles setTimeout(() => input.focus(), 200); } // ---- Search highlight logic ---- function clearSfxHighlights() { sfxHighlights.forEach(el => { const parent = el.parentNode; if (parent) { parent.replaceChild(document.createTextNode(el.textContent), el); parent.normalize(); } }); sfxHighlights = []; sfxCurrentIdx = -1; } function runSfxSearch(query) { clearSfxHighlights(); if (!query || query.length < 1) { counter.textContent = ''; prevBtn.disabled = true; nextBtn.disabled = true; return; } const lq = query.toLowerCase(); // Only search inside the article content (drama list) const article = document.querySelector('.entry-text article'); if (!article) { counter.textContent = 'No content'; return; } // Walk text nodes inside the article, skipping links and script/style const walker = document.createTreeWalker(article, NodeFilter.SHOW_TEXT, { acceptNode: n => { const el = n.parentElement; if (!el) return NodeFilter.FILTER_REJECT; const tag = el.tagName; if (['SCRIPT','STYLE','NOSCRIPT','A'].includes(tag)) return NodeFilter.FILTER_REJECT; if (el.closest('a')) return NodeFilter.FILTER_REJECT; // skip nested link text return n.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } }); const textNodes = []; let node; while ((node = walker.nextNode())) textNodes.push(node); textNodes.forEach(tn => { const fullText = tn.textContent; // Process line by line — only highlight within drama name lines const lines = fullText.split('\n'); let anyMatch = false; const frag = document.createDocumentFragment(); lines.forEach((line, lineIdx) => { const dramaName = extractDramaName(line); if (dramaName && dramaName.toLowerCase().includes(lq)) { // This line has a drama name that matches — highlight occurrences of query const lLine = line.toLowerCase(); let last = 0; let pos = lLine.indexOf(lq); while (pos !== -1) { if (pos > last) frag.appendChild(document.createTextNode(line.slice(last, pos))); const mark = document.createElement('mark'); mark.className = 'kdrama-highlight'; mark.textContent = line.slice(pos, pos + lq.length); frag.appendChild(mark); sfxHighlights.push(mark); last = pos + lq.length; pos = lLine.indexOf(lq, last); } if (last < line.length) frag.appendChild(document.createTextNode(line.slice(last))); anyMatch = true; } else { frag.appendChild(document.createTextNode(line)); } // Re-add the newline between lines (but not after the very last) if (lineIdx < lines.length - 1) frag.appendChild(document.createTextNode('\n')); }); if (anyMatch && tn.parentNode) tn.parentNode.replaceChild(frag, tn); }); if (sfxHighlights.length === 0) { counter.textContent = 'No results'; prevBtn.disabled = true; nextBtn.disabled = true; } else { sfxCurrentIdx = 0; jumpToSfxMatch(0); prevBtn.disabled = false; nextBtn.disabled = false; } } function jumpToSfxMatch(idx) { sfxHighlights.forEach((el, i) => el.classList.toggle('current', i === idx)); const el = sfxHighlights[idx]; if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); counter.textContent = `${idx + 1} / ${sfxHighlights.length}`; } } // ---- Events ---- let searchDebounce = null; const triggerSearch = () => { clearTimeout(searchDebounce); searchDebounce = setTimeout(() => runSfxSearch(input.value.trim()), 280); }; input.addEventListener('input', triggerSearch); // Collapse on blur only when no active search results. // While results are shown the user needs the bar to navigate between them. input.addEventListener('blur', e => { if (sfxHighlights.length > 0) return; // keep open while navigating results if (wrap.contains(e.relatedTarget)) return; // keep open when focusing bar buttons collapse(); }); input.addEventListener('keydown', e => { if (e.key === 'Enter') { if (sfxHighlights.length === 0) return; sfxCurrentIdx = e.shiftKey ? (sfxCurrentIdx - 1 + sfxHighlights.length) % sfxHighlights.length : (sfxCurrentIdx + 1) % sfxHighlights.length; jumpToSfxMatch(sfxCurrentIdx); } else if (e.key === 'Escape') { collapse(); } }); prevBtn.addEventListener('click', () => { if (sfxHighlights.length === 0) return; sfxCurrentIdx = (sfxCurrentIdx - 1 + sfxHighlights.length) % sfxHighlights.length; jumpToSfxMatch(sfxCurrentIdx); input.focus(); }); nextBtn.addEventListener('click', () => { if (sfxHighlights.length === 0) return; sfxCurrentIdx = (sfxCurrentIdx + 1) % sfxHighlights.length; jumpToSfxMatch(sfxCurrentIdx); input.focus(); }); closeBtn.addEventListener('click', e => { e.stopPropagation(); clearSfxHighlights(); input.value = ''; counter.textContent = ''; prevBtn.disabled = true; nextBtn.disabled = true; collapse(); }); // Clicking the collapsed circle expands it wrap.addEventListener('click', e => { if (isCollapsed) { expand(); } }); // Collapse on user scroll only when no active search results. let scrollListenerActive = false; setTimeout(() => { scrollListenerActive = true; }, 300); window.addEventListener('scroll', () => { if (!scrollListenerActive) return; if (sfxHighlights.length > 0) return; // keep open while navigating results if (!isCollapsed) collapse(); }, { passive: true }); // Clicking anywhere outside the bar always collapses it (even with active results). document.addEventListener('click', e => { if (!isCollapsed && !wrap.contains(e.target)) { collapse(); } }, { capture: true, passive: true }); } // --- FileDitch page handler: auto-clicks the download button and closes the popup --- // Runs on fileditchfiles.me when the page is opened by the orange circle button. function handleFileDitchPage() { if (!window.location.hostname.includes('fileditchfiles.me')) return; if (!window.opener) return; function tryClick() { const btn = document.querySelector('a.btn.btn-main[download], a.btn-main[download]'); if (!btn || !btn.href) return false; btn.click(); setTimeout(() => { try { window.close(); } catch(_) {} }, 3000); return true; } if (!tryClick()) { // Button not in DOM yet — wait for it const obs = new MutationObserver(() => { if (tryClick()) obs.disconnect(); }); obs.observe(document.body || document.documentElement, { childList: true, subtree: true }); // Give up after 15s and close setTimeout(() => { obs.disconnect(); try { window.close(); } catch(_) {} }, 15000); } } // --- Mega.nz Floating Bypass Button --- function enhanceMegaNzPage() { if (!config.megaNzButton) return; if (!window.location.hostname.includes('mega.nz')) return; // Only act on file pages if (!window.location.href.includes('/file/')) return; GM_addStyle(` #sinflix-mega-bypass-btn { position: fixed; bottom: 28px; left: 50%; transform: translateX(-50%); z-index: 99999; display: inline-flex; align-items: center; gap: 10px; padding: 0 24px; height: 48px; border-radius: 24px; border: 1px solid rgba(217, 39, 46, 0.45); background: rgba(28, 28, 28, 0.92); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: 600; letter-spacing: 0.2px; cursor: pointer; white-space: nowrap; box-shadow: 0 4px 24px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.06); transition: background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease, transform 0.15s ease; user-select: none; } #sinflix-mega-bypass-btn:hover { background: rgba(217, 39, 46, 0.88); border-color: rgba(217, 39, 46, 0.9); box-shadow: 0 6px 28px rgba(217,39,46,0.35), 0 2px 8px rgba(0,0,0,0.4); transform: translateX(-50%) translateY(-2px); } #sinflix-mega-bypass-btn:active { transform: translateX(-50%) translateY(0px); box-shadow: 0 2px 12px rgba(217,39,46,0.25); } #sinflix-mega-bypass-btn .sfx-mega-icon { width: 18px; height: 18px; opacity: 0.9; flex-shrink: 0; } #sinflix-mega-bypass-btn .sfx-mega-badge { font-size: 10px; font-weight: 700; color: #d9272e; background: rgba(217,39,46,0.15); border: 1px solid rgba(217,39,46,0.35); border-radius: 4px; padding: 1px 5px; letter-spacing: 0.5px; text-transform: uppercase; } `); const btn = document.createElement('button'); btn.id = 'sinflix-mega-bypass-btn'; btn.title = 'Download via bypass — skips Mega quota limits'; btn.innerHTML = ` Download Bypass`; // Wait for body to be ready then append const appendBtn = () => document.body && document.body.appendChild(btn); if (document.body) appendBtn(); else document.addEventListener('DOMContentLoaded', appendBtn); btn.addEventListener('click', () => { const bypassUrl = getMegaBypassUrl(window.location.href); showNotification('Download started…', 'success', 2500); triggerDownload(bypassUrl); }); } // --- Enhanced Initialization --- function initialize() { if (window.location.hostname === 'pst.moe') { try { enhancePstMoeContent(); } catch (e) { console.error('Sinflix Modifier error during pst.moe enhancement:', e); } // For pst.moe, we just setup the UI and stop rentry-specific logic createSettingsUI(); return; } if (window.location.hostname.includes('fileditchfiles.me')) { try { handleFileDitchPage(); } catch(e) { console.error('Sinflix Modifier error on fileditchfiles.me:', e); } return; } if (window.location.hostname.includes('mega.nz')) { try { enhanceMegaNzPage(); } catch (e) { console.error('Sinflix Modifier error during mega.nz enhancement:', e); } // No settings gear on mega.nz — keep the page clean return; } if (window.location.hostname.includes('buzzheavier.com')) { try { enhanceBuzzheavierContent(); } catch (e) { console.error('Sinflix Modifier error during buzzheavier enhancement:', e); } createSettingsUI(); return; } // Immediate content enhancement - no delay try { enhancePageContent(); } catch (e) { console.error('Sinflix Modifier error during immediate enhancement:', e); } createSettingsUI(); createFloatingButtons(); createTopSearchBar(); const settingsModal = document.getElementById('kdrama-settings-modal'); window.addEventListener('scroll', () => { if (settingsModal && settingsModal.style.display === 'flex') { if (document.activeElement !== document.getElementById('kdrama-search-input')) { settingsModal.style.display = 'none'; } } }); // Single late-fallback retry in case the MutationObserver missed the content // (e.g. content was already in the DOM but not yet observed). Fires after // the page is fully interactive to avoid blocking early user input. setTimeout(() => { try { enhancePageContent(); } catch (e) { console.error('Sinflix Modifier error during fallback enhancement:', e); } }, 1200); if (typeof MutationObserver !== 'undefined') { // Debounce: wait 150ms after the last mutation before processing. // This prevents dozens of expensive TreeWalker scans from firing // back-to-back during the initial page load, which would block // the main thread and make clicks unresponsive. let mutationTimer = null; const debouncedEnhance = () => { clearTimeout(mutationTimer); mutationTimer = setTimeout(() => { try { enhancePageContent(); } catch (e) { console.error('Sinflix Modifier error during MutationObserver processing:', e); } }, 150); }; const observer = new MutationObserver((mutations) => { let shouldProcess = false; mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.addedNodes.length > 0) { if (mutation.target.closest('.entry-text article') || mutation.target.matches('.entry-text article')) { shouldProcess = true; } } }); if (shouldProcess) { debouncedEnhance(); } }); const content = document.querySelector('.entry-text article'); if (content) { observer.observe(content, { childList: true, subtree: true }); // Also process immediately if content already exists try { enhancePageContent(); } catch (e) { console.error('Sinflix Modifier error during immediate content processing:', e); } } else { const bodyObserver = new MutationObserver((mutations, obs) => { if (document.querySelector('.entry-text article')) { obs.disconnect(); const foundContent = document.querySelector('.entry-text article'); observer.observe(foundContent, { childList: true, subtree: true }); debouncedEnhance(); } }); bodyObserver.observe(document.body, { childList: true, subtree: true }); } } } // Initialize the script — single entry point to avoid main-thread contention. // Previously, enhancePageContent() was fired 5+ times simultaneously // (script-start, readystatechange, DOMContentLoaded, setInterval × N), // blocking the main thread for 1-2 s and making clicks/scrolls unresponsive. if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialize); } else { initialize(); } })(); // ---