// ==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(/
, 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 = `