您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Расширение и перемещение панели эмодзи FFZ
当前为
// ==UserScript== // @name FFZ Panel Resize 1.2.27 // @namespace http://tampermonkey.net/ // @version 1.2.27 // @description Расширение и перемещение панели эмодзи FFZ // @author gullampis810 // @match https://www.twitch.tv/* // @license MIT // @icon https://png.pngtree.com/png-vector/20220703/ourmid/pngtree-send-dark-mode-glyph-icon-png-image_5561369.png // @grant GM_addStyle // @run-at document-idle // @downloadURL // @updateURL // ==/UserScript== (function() { 'use strict'; // кнопка Chat Paused Due to Scroll // const observer = new MutationObserver(() => { const buttonContainer = document.querySelector('.tw-absolute.tw-border-radius-medium.tw-bottom-0.tw-c-background-overlay.tw-c-text-overlay.tw-mg-b-1'); if (buttonContainer) { buttonContainer.style.height = '34px'; buttonContainer.style.minHeight = '34px'; buttonContainer.style.maxHeight = '34px'; console.log('Высота контейнера кнопки установлена на 34px'); } }); observer.observe(document.body, { childList: true, subtree: true }); // Добавляем стили для изменения размеров контейнера эмодзи GM_addStyle(` .emote-picker__controls-container.tw-relative { bottom: 3px !important; } .emote-picker { width: 107rem !important; /* Увеличенная ширина */ height: 100rem !important; /* Увеличенная высота */ left: 24px !important;; /* Сдвиг влево */ position: relative !important; } .ffz--emote-picker { position: relative !important; height: 785px !important; width: 1097px !important; left: -243px !important; } .ffz--emote-picker.ffz--emote-picker__tall .emote-picker__nav-content-overflow, .ffz--emote-picker.ffz--emote-picker__tall .emote-picker__tab-content { height: unset !important; max-height: 73rem !important; } .tw-absolute.ffz-attached.ffz-attached--right.ffz-attached--up { width: 857px !important; right: 368px !important; bottom: 533px !important; } /* fix ballon when clicked ffz emote picke in chat input */ .ffz-balloon.ffz-balloon--auto.tw-inline-block.tw-border-radius-large.tw-c-background-base.tw-c-text-inherit.tw-elevation-2.ffz--emote-picker.ffz--emote-picker__tall { top: 290px !important; } .ffz-attached--up { bottom: 510% !important; } .tw-border-b.tw-border-l.tw-border-r.tw-border-t.tw-border-radius-medium.tw-c-background-base.tw-elevation-1 { width: 63px; !important; height: 216px; !important; } .tw-absolute { position: absolute !important; height: 570px !important; } .tw-border-b.tw-border-l.tw-border-r.tw-border-t.tw-border-radius-medium.tw-c-background-base.tw-elevation-1 { width: 60px !important; height: 200px !important; bottom: 6px !important; position: absolute !important; right: 5px !important; } /* emoji standard color choice mini panel */ `); console.log("[FFZ Emote Panel] Контейнер .emote-picker изменен: шире, выше, сдвинут влево."); })(); // ============== FFZ Dialog Unconstrained Dragging свободное перемещение Панели ffz Settings main-menu vue js css ============ // (function() { 'use strict'; console.log('[FFZ Dialog Unconstrained Dragging v1.2] Script started'); // Функция для инициализации перетаскивания function initDragging(dialog) { if (!dialog) { console.log('[FFZ Dialog Unconstrained Dragging] Dialog not found'); return; } const header = dialog.querySelector('header'); if (!header) { console.log('[FFZ Dialog Unconstrained Dragging] Header not found'); return; } // Проверяем, не инициализировано ли уже if (dialog.dataset.draggingInitialized) { console.log('[FFZ Dialog Unconstrained Dragging] Dragging already initialized, skipping'); return; } dialog.dataset.draggingInitialized = 'true'; console.log('[FFZ Dialog Unconstrained Dragging] Initializing dragging for dialog'); // Переменные для перетаскивания let isDragging = false; let startX, startY; // Обработчик начала перетаскивания header.addEventListener('mousedown', (e) => { // Игнорируем клики по кнопкам if (e.target.closest('button')) { console.log('[FFZ Dialog Unconstrained Dragging] Ignoring button click'); return; } isDragging = true; startX = e.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft); startY = e.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop); // Повышаем z-index dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1; console.log('[FFZ Dialog Unconstrained Dragging] Drag started at', e.clientX, e.clientY); e.preventDefault(); e.stopPropagation(); // Останавливаем оригинальные обработчики }, { capture: true, passive: false }); // Обработчик движения мыши document.addEventListener('mousemove', (e) => { if (!isDragging) return; requestAnimationFrame(() => { const newLeft = e.clientX - startX; const newTop = e.clientY - startY; dialog.style.left = `${newLeft}px`; dialog.style.top = `${newTop}px`; console.log('[FFZ Dialog Unconstrained Dragging] Moved to', newLeft, newTop); }); }, { capture: true, passive: true }); // Обработчик окончания перетаскивания document.addEventListener('mouseup', () => { if (isDragging) { console.log('[FFZ Dialog Unconstrained Dragging] Drag ended'); isDragging = false; } }, { capture: true, passive: true }); // Поддержка сенсорных устройств header.addEventListener('touchstart', (e) => { if (e.target.closest('button')) { console.log('[FFZ Dialog Unconstrained Dragging] Ignoring button touch'); return; } isDragging = true; const touch = e.touches[0]; startX = touch.clientX - (parseFloat(dialog.style.left) || dialog.offsetLeft); startY = touch.clientY - (parseFloat(dialog.style.top) || dialog.offsetTop); dialog.style.zIndex = Math.max(parseInt(dialog.style.zIndex) || 9000, 9000) + 1; console.log('[FFZ Dialog Unconstrained Dragging] Touch drag started at', touch.clientX, touch.clientY); e.preventDefault(); e.stopPropagation(); }, { capture: true, passive: false }); document.addEventListener('touchmove', (e) => { if (!isDragging) return; const touch = e.touches[0]; requestAnimationFrame(() => { const newLeft = touch.clientX - startX; const newTop = touch.clientY - startY; dialog.style.left = `${newLeft}px`; dialog.style.top = `${newTop}px`; console.log('[FFZ Dialog Unconstrained Dragging] Touch moved to', newLeft, newTop); }); }, { capture: true, passive: true }); document.addEventListener('touchend', () => { if (isDragging) { console.log('[FFZ Dialog Unconstrained Dragging] Touch drag ended'); isDragging = false; } }, { capture: true, passive: true }); // Добавляем кнопку для сброса позиции const resetButton = document.createElement('button'); resetButton.textContent = 'Reset Position'; resetButton.style.position = 'absolute'; resetButton.style.top = '625px'; resetButton.style.right = ' 5px'; resetButton.style.zIndex = '10000'; resetButton.style.padding = '5px'; resetButton.style.background = '#34767c'; resetButton.style.borderRadius = '12px'; resetButton.style.border = '1px solid #ffffff'; resetButton.addEventListener('click', () => { dialog.style.left = '25%'; dialog.style.top = '25%'; console.log('[FFZ Dialog Unconstrained Dragging] Position reset to 25%, 25%'); }); dialog.appendChild(resetButton); console.log('[FFZ Dialog Unconstrained Dragging] Dragging initialized successfully'); } // Наблюдатель за появлением диалога const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.addedNodes.length) { const dialog = document.querySelector('.ffz-dialog.ffz-main-menu:not([data-dragging-initialized])'); if (dialog) { console.log('[FFZ Dialog Unconstrained Dragging] Dialog detected via observer'); initDragging(dialog); } } } }); // Запускаем наблюдатель observer.observe(document.body, { childList: true, subtree: true }); // Проверяем, если диалог уже существует const initialDialog = document.querySelector('.ffz-dialog.ffz-main-menu:not([data-dragging-initialized])'); if (initialDialog) { console.log('[FFZ Dialog Unconstrained Dragging] Initial dialog found'); initDragging(initialDialog); } console.log('[FFZ Dialog Unconstrained Dragging] Setup complete'); })(); // ======= ffz-viewer-card move freedom ========= // (function() { 'use strict'; // Inject CSS for scrollable modified emotes list GM_addStyle(` .ffz-emote-card__modifiers { max-height: 200px !important; overflow-y: auto !important; overflow-x: hidden !important; padding-right: 8px !important; } .ffz-emote-card__modifiers::-webkit-scrollbar { width: 8px; } .ffz-emote-card__modifiers::-webkit-scrollbar-track { background: rgba(0, 0, 0, 0.1); } .ffz-emote-card__modifiers::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.3); border-radius: 4px; } .ffz-emote-card__modifiers > div { width: 100%; box-sizing: border-box; } `); // Function to enable unconstrained dragging for ffz-viewer-card function enableUnconstrainedDragging() { // Find the ffz-viewer-card element const observer = new MutationObserver((mutations, obs) => { const viewerCard = document.querySelector('.ffz-viewer-card.tw-border'); if (viewerCard) { // Initialize custom drag functionality setupCustomDrag(viewerCard); // Stop observing once the card is found obs.disconnect(); } }); // Observe the document for the viewer card observer.observe(document.body, { childList: true, subtree: true }); } // Custom drag implementation function setupCustomDrag(card) { const header = card.querySelector('.ffz-viewer-card__header'); if (!header) { console.log('[FFZ Enhancements] Header not found for dragging'); return; } let isDragging = false; let currentX; let currentY; let initialX; let initialY; header.addEventListener('mousedown', (e) => { // Ignore if clicking on elements with viewer-card-drag-cancel if (e.target.closest('.viewer-card-drag-cancel')) return; isDragging = true; initialX = e.clientX - currentX; initialY = e.clientY - currentY; card.style.transition = 'none'; // Disable transitions during drag e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; currentX = e.clientX - initialX; currentY = e.clientY - initialY; card.style.left = `${currentX}px`; card.style.top = `${currentY}px`; }); document.addEventListener('mouseup', () => { isDragging = false; card.style.transition = ''; // Restore transitions }); // Initialize position if not set if (!card.style.left || !card.style.top) { const rect = card.getBoundingClientRect(); currentX = rect.left; currentY = rect.top; card.style.left = `${currentX}px`; card.style.top = `${currentY}px`; } } // Re-integrate showEmoteSelectionPopup to follow the draggable card function showEmoteSelectionPopup(emotes, callback) { console.log("[FFZ Enhancements] Attempting to show emote selection popup with emotes:", emotes); // Remove existing popup const existingPopup = document.getElementById('emote-selection-popup'); if (existingPopup) { console.log("[FFZ Enhancements] Removing existing popup"); existingPopup.remove(); } // Create popup const popup = document.createElement('div'); popup.id = 'emote-selection-popup'; popup.innerHTML = ` <div class="close-button" style="cursor:pointer;position:absolute;top:6px;right:10px;font-size:20px;">✕</div> <div class="emote-options"></div> `; document.body.appendChild(popup); console.log("[FFZ Enhancements] Popup element created and appended to body"); // Inline styles for popup popup.style.position = 'fixed'; popup.style.background = 'rgb(56, 90, 80)'; popup.style.color = 'rgb(235, 235, 235)'; popup.style.fontWeight = 'bold'; popup.style.fontSize = '16px'; popup.style.border = '1px solid #12b6a7'; popup.style.borderRadius = '8px'; popup.style.padding = '10px 10px 10px 10px'; popup.style.zIndex = '10001'; popup.style.maxWidth = '320px'; popup.style.maxHeight = '500px'; popup.style.overflowY = 'auto'; popup.style.transition = 'opacity 0.2s ease, transform 0.2s ease'; popup.style.opacity = '0'; popup.style.transform = 'scale(0.9)'; popup.style.display = 'block'; popup.style.visibility = 'visible'; popup.style.paddingTop = '32px'; // Position popup relative to ffz-viewer-card const viewerCard = document.querySelector('.ffz-viewer-card.tw-border'); if (viewerCard) { const rect = viewerCard.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const popupWidth = 320; const offset = 20; const extraOffset = 30; let left = rect.right + offset + extraOffset; let top = rect.top; if (left + popupWidth > viewportWidth) { left = rect.left - popupWidth - offset; } if (top + popup.offsetHeight > viewportHeight) { top = viewportHeight - popup.offsetHeight - offset; } if (top < 0) { top = offset; } if (left < 0) { left = offset; if (rect.bottom + popup.offsetHeight + offset <= viewportHeight) { left = rect.left; top = rect.bottom + offset; } } popup.style.left = `${left}px`; popup.style.top = `${top}px`; console.log("[FFZ Enhancements] Popup positioned at left:", left, "top:", top); } else { popup.style.right = '310px'; popup.style.top = '385px'; console.warn("[FFZ Enhancements] ffz-viewer-card not found, using fallback position"); } const optionsContainer = popup.querySelector('.emote-options'); // Populate popup emotes.forEach((emote, index) => { const option = document.createElement('div'); option.className = 'emote-option'; option.style.display = 'flex'; option.style.alignItems = 'center'; option.style.justifyContent = 'space-between'; option.style.padding = '8px 0'; option.style.borderBottom = '1px solid rgba(115, 209, 204, 0.16)'; option.style.gap = '10px'; const left = document.createElement('div'); left.style.display = 'flex'; left.style.alignItems = 'center'; left.style.minWidth = '0'; const img = document.createElement('img'); img.src = emote.src || ''; img.alt = emote.alt || 'Emote'; img.style.width = '24px'; img.style.height = '24px'; img.style.marginRight = '10px'; img.style.flexShrink = '0'; img.style.userSelect = 'none'; const info = document.createElement('div'); info.className = 'emote-info'; info.style.fontSize = '14px'; info.style.whiteSpace = 'nowrap'; info.style.overflow = 'hidden'; info.style.textOverflow = 'ellipsis'; info.innerHTML = `<span>${emote.alt || 'Unnamed'} <span style="user-select:none;">(${emote.platform})</span></span>`; left.appendChild(img); left.appendChild(info); const blockButton = document.createElement('button'); blockButton.className = 'block-button'; blockButton.type = 'button'; blockButton.textContent = 'Block'; blockButton.style.background = '#ff5555'; blockButton.style.color = '#ffffff'; blockButton.style.border = 'none'; blockButton.style.padding = '4px 8px'; blockButton.style.borderRadius = '4px'; blockButton.style.cursor = 'pointer'; blockButton.style.fontSize = '12px'; blockButton.style.marginLeft = '10px'; blockButton.style.flexShrink = '0'; blockButton.style.userSelect = 'none'; blockButton.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); console.log("[FFZ Enhancements] Block button clicked for emote:", emote); callback(emote); if (emote.element) { emote.element.style.display = 'none'; console.log("[FFZ Enhancements] Emote element hidden:", emote.alt); const parentContainer = emote.element.closest('.ffz--inline, .chat-line__message, .chat-image'); if (parentContainer) { const allEmotes = parentContainer.querySelectorAll( 'img.chat-line__message--emote, .ffz-emote, .seventv-emote, .bttv-emote, .twitch-emote, .chat-image' ); const allBlocked = Array.from(allEmotes).every(e => e.style.display === 'none'); if (allBlocked) { parentContainer.style.display = 'none'; console.log("[FFZ Enhancements] Parent container hidden as all emotes are blocked"); } } } popup.remove(); }); option.appendChild(left); option.appendChild(blockButton); optionsContainer.appendChild(option); }); console.log("[FFZ Enhancements] Popup populated with", emotes.length, "emotes"); // Close button const closeButton = popup.querySelector('.close-button'); closeButton.onclick = () => { console.log("[FFZ Enhancements] Emote selection popup closed via close button"); popup.remove(); }; // Ensure visibility const computedStyle = window.getComputedStyle(popup); if (computedStyle.display === 'none' || computedStyle.visibility === 'hidden') { console.warn("[FFZ Enhancements] Popup is not visible, forcing visibility"); popup.style.display = 'block'; popup.style.visibility = 'visible'; } // Animation setTimeout(() => { popup.classList.add('visible'); popup.style.opacity = '1'; popup.style.transform = 'scale(1)'; console.log("[FFZ Enhancements] Popup visibility class and styles applied"); }, 10); // Close on outside click document.addEventListener('click', function closePopup(e) { if (!popup.contains(e.target) && e.target !== popup && !viewerCard.contains(e.target)) { console.log("[FFZ Enhancements] Closing popup due to outside click"); popup.remove(); document.removeEventListener('click', closePopup); } }, { capture: true, once: true }); // Observe viewerCard for position changes const observer = new MutationObserver(() => { if (viewerCard) { const rect = viewerCard.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; const popupWidth = 320; const offset = 20; const extraOffset = 30; let left = rect.right + offset + extraOffset; let top = rect.top; if (left + popupWidth > viewportWidth) { left = rect.left - popupWidth - offset; } if (top + popup.offsetHeight > viewportHeight) { top = viewportHeight - popup.offsetHeight - offset; } if (top < 0) { top = offset; } if (left < 0) { left = offset; if (rect.bottom + popup.offsetHeight + offset <= viewportHeight) { left = rect.left; top = rect.bottom + offset; } } popup.style.left = `${left}px`; popup.style.top = `${top}px`; console.log("[FFZ Enhancements] Popup repositioned to left:", left, "top:", top); } }); if (viewerCard) { observer.observe(viewerCard, { attributes: true, attributeFilter: ['style', 'class'] }); } popup.addEventListener('remove', () => { observer.disconnect(); console.log("[FFZ Enhancements] MutationObserver disconnected"); }); } // Initialize enhancements enableUnconstrainedDragging(); // Expose showEmoteSelectionPopup globally for external use window.showEmoteSelectionPopup = showEmoteSelectionPopup; })();