您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically enters CSS web fullscreen on video or image load and clicks 'next' on video end or image showed after 2s. Toggle with 'G' key.
// ==UserScript== // @name Telegram Auto Next & CSS Fullscreen // @namespace http://tampermonkey.net/ // @version 2.1 // @description Automatically enters CSS web fullscreen on video or image load and clicks 'next' on video end or image showed after 2s. Toggle with 'G' key. // @author CurssedCoffin (perfected with gemini) https://github.com/CurssedCoffin // @match https://web.telegram.org/a/* // @icon https://www.google.com/s2/favicons?sz=64&domain=telegram.org // @grant GM_setValue // @grant GM_getValue // @run-at document-idle // @license MIT // ==/UserScript== (function() { 'use strict'; // Enforces stricter parsing and error handling in JavaScript. // --- Log Function --- /** * Logs a message to the console with a specific script prefix for easy debugging. * @param {string} message The message to log. */ function log(message) { // Prepends a unique identifier to all console messages from this script. console.log(`[TG Media Enhancer] ${message}`); } // --- Configuration --- // The interval (in milliseconds) at which the script checks the state of the media viewer. const CHECK_INTERVAL_MS = 250; // The delay (in milliseconds) before automatically switching to the next image. const IMAGE_SWITCH_DELAY_MS = 2000; // The time threshold (in seconds) from the end of a video to trigger the switch to the next media. const VIDEO_END_THRESHOLD_S = 1.0; // The key used to toggle the script's enabled/disabled state. const TOGGLE_KEY = 'g'; // A unique ID for the CSS <style> element injected by this script for fullscreen mode. const FULLSCREEN_STYLE_ID = 'tg-enhancer-fullscreen-style'; // --- State Variables --- // Holds the current enabled/disabled state of the script. // The state is persisted across sessions using GM_getValue. Defaults to 'true' (enabled). let isEnabled = GM_getValue('tgMediaEnhancerEnabled', true); // Holds the timer ID for the automatic image switch. Used to cancel the timer if needed. let imageSwitchTimeout = null; // Stores a reference to the currently displayed media element to prevent reprocessing it. let processedMediaElement = null; log(`Initial state loaded. Enabled: ${isEnabled}`); /** * Shows a temporary notification overlay on the screen. * @param {string} message The message to display. */ function showNotification(message) { log(`Showing notification: "${message}"`); // Find and remove any existing notification to prevent overlap. const existing = document.getElementById('tg-enhancer-notification'); if (existing) { log("Removing existing notification."); existing.remove(); } // Create the notification element. const notification = document.createElement('div'); notification.id = 'tg-enhancer-notification'; // Apply CSS styles for positioning, appearance, and transitions. Object.assign(notification.style, { position: 'fixed', bottom: '20px', left: '50%', transform: 'translateX(-50%)', padding: '10px 20px', background: 'rgba(0, 0, 0, 0.75)', color: 'white', zIndex: '9999', borderRadius: '8px', opacity: '1', transition: 'opacity 0.5s ease-out', pointerEvents: 'none', }); notification.textContent = message; document.body.appendChild(notification); log("Notification element appended to body."); // Set a timer to fade out and then remove the notification. setTimeout(() => { log("Fading out notification."); notification.style.opacity = '0'; setTimeout(() => { log("Removing notification from DOM."); notification.remove(); }, 500); // Wait for fade-out transition to complete before removing. }, 2000); // Notification stays visible for 2 seconds. } /** * Removes the fullscreen CSS style from the document head, returning to the default view. */ function disableFullscreen() { log("Attempting to disable fullscreen."); const styleTag = document.getElementById(FULLSCREEN_STYLE_ID); if (styleTag) { styleTag.remove(); log("Fullscreen style tag found and removed."); } else { log("Fullscreen style tag not found, no action needed."); } } /** * Creates or updates the CSS style for a clean, letterboxed/pillarboxed fullscreen media display. * It calculates the correct scale and translation to fit the media to the screen. * @param {HTMLElement} contentElement The element containing the media. */ function updateOrCreateFullscreenStyle(contentElement, mediaViewer) { log("Attempting to update or create fullscreen style."); // Find the elements that define the media's dimensions. const videoSizer = contentElement.querySelector('.VideoPlayer > div'); const imageElement = contentElement.querySelector('img'); let mediaWidth = 0, mediaHeight = 0; // Get dimensions from the video sizer element if it exists. if (videoSizer && videoSizer.style.width) { mediaWidth = parseFloat(videoSizer.style.width); mediaHeight = parseFloat(videoSizer.style.height); log(`Video sizer found. Dimensions: ${mediaWidth}x${mediaHeight}`); // Otherwise, get dimensions from the image's natural size. } else if (imageElement) { mediaWidth = imageElement.naturalWidth; mediaHeight = imageElement.naturalHeight; log(`Image element found. Natural dimensions: ${mediaWidth}x${mediaHeight}`); } else { log("No video sizer or image element found for style creation."); } // Abort if media dimensions are not valid. if (isNaN(mediaWidth) || mediaWidth === 0) { log("Media width is invalid or zero, aborting style creation."); return; } // Calculate the scale factor to fit the media within the viewport while maintaining aspect ratio. const screenWidth = window.innerWidth, screenHeight = window.innerHeight; const scale = Math.min(screenWidth / mediaWidth, screenHeight / mediaHeight); // Calculate the translation needed to center the scaled media on the screen. const translateX = (screenWidth - (mediaWidth * scale)) / 2; const translateY = (screenHeight - (mediaHeight * scale)) / 2; // Construct the CSS transform value. const newTransform = `translate3d(${translateX.toFixed(4)}px, ${translateY.toFixed(4)}px, 0px) scale3d(${scale.toFixed(4)}, ${scale.toFixed(4)}, 1)`; log(`Calculated fullscreen transform: ${newTransform}`); // Define the CSS rule to override Telegram's default styles for the active media slide. const newRule = ` #MediaViewer .MediaViewerSlide--active .MediaViewerContent { position: fixed !important; top: 0 !important; left: 0 !important; width: ${mediaWidth}px !important; height: ${mediaHeight}px !important; transform-origin: 0 0 !important; transform: ${newTransform} !important; z-index: 1500 !important; } `; // Find the existing style tag or create a new one. let styleTag = document.getElementById(FULLSCREEN_STYLE_ID); if (!styleTag) { log("No existing style tag found, creating a new one."); styleTag = document.createElement('style'); styleTag.id = FULLSCREEN_STYLE_ID; document.head.appendChild(styleTag); log("New style tag appended to head."); } // Update the style tag's content only if it has changed. if (styleTag.textContent !== newRule) { log("Updating style tag content."); styleTag.textContent = newRule; } else { log("Style rule is already up-to-date."); } // For a cleaner view, remove the default header and caption. const mediaHead = mediaViewer.querySelector('.media-viewer-head'); if (mediaHead) { mediaHead.remove(); log("Removed media viewer header."); } const mediaText = contentElement.querySelector('.media-viewer-footer-content'); if (mediaText) { mediaText.remove(); log("Removed media text overlay."); } } /** * Dispatches a keyboard event to the document to simulate pressing the right arrow key, * which is Telegram's native way to advance to the next media item. */ function switchToNext() { log("Dispatching 'ArrowRight' keydown event to switch to next media."); const rightArrowEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', keyCode: 39, which: 39, bubbles: true, cancelable: true }); document.dispatchEvent(rightArrowEvent); } /** * The main function of the script, called periodically by setInterval. * It checks the current state of the Telegram media viewer and takes action. */ function checkViewState() { // Check if the main media viewer container is present in the DOM. const mediaViewer = document.getElementById('MediaViewer'); if (!mediaViewer) { // If the viewer is closed, perform cleanup. if (processedMediaElement) { log("Media viewer is closed. Cleaning up state."); clearTimeout(imageSwitchTimeout); // Clear any pending image switch. log("Image switch timeout cleared."); processedMediaElement = null; // Reset the state. log("Processed media element reset."); disableFullscreen(); // Remove the custom fullscreen styles. } return; // Exit the function since the viewer isn't open. } // If the script is manually disabled, perform cleanup and exit. if (!isEnabled) { if (processedMediaElement) { log("Script is disabled but state exists. Cleaning up."); clearTimeout(imageSwitchTimeout); log("Image switch timeout cleared because script is disabled."); processedMediaElement = null; log("Processed media element reset because script is disabled."); disableFullscreen(); } return; } // Find the currently active media slide. const activeSlide = mediaViewer.querySelector('.MediaViewerSlide--active'); if (!activeSlide) { // This can happen briefly during transitions, so we just wait for the next check. return; } // Find the container and the actual media element (video or image). const contentElement = activeSlide.querySelector('.MediaViewerContent'); const currentElement = activeSlide.querySelector('video, img'); if (!contentElement || !currentElement) { log("No content or media element (video/img) found in the active slide."); return; } // This is the core logic: check if the displayed media is a new one. if (currentElement !== processedMediaElement) { log(`New media detected. Old: ${processedMediaElement?.tagName}, New: ${currentElement.tagName}.`); processedMediaElement = currentElement; // Update state to the new element. log("Clearing any existing image switch timeout."); clearTimeout(imageSwitchTimeout); // Cancel previous timer. updateOrCreateFullscreenStyle(contentElement, mediaViewer); // Apply fullscreen styles. // If the new media is an image, set a timer to switch to the next one. if (currentElement.tagName === 'IMG') { log(`Setting up timer to switch image in ${IMAGE_SWITCH_DELAY_MS}ms.`); imageSwitchTimeout = setTimeout(() => { log("Image timer expired. Triggering switch to next media."); processedMediaElement = null; // Reset state *before* switching to allow the next media to be processed. switchToNext(); }, IMAGE_SWITCH_DELAY_MS); } return; // Return after processing the new media. } // If the media is a video that we are already tracking: if (currentElement.tagName === 'VIDEO') { const video = currentElement; // Check if the video is near its end. const isNearEnd = video.duration && (video.duration - video.currentTime) < VIDEO_END_THRESHOLD_S; if (isNearEnd) { log(`Video is near end (currentTime: ${video.currentTime}, duration: ${video.duration}). Triggering switch.`); processedMediaElement = null; // Reset state before switching. switchToNext(); } } } // Add a global keyboard listener to handle the toggle key. document.addEventListener('keydown', (e) => { log(`Keydown event detected: Key='${e.key}', Target='${e.target.tagName}'.`); // Ignore key presses if the user is typing in a text field to prevent conflicts. if (e.target.isContentEditable || e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') { log("Keydown event ignored (target is content-editable or an input field)."); return; } // Check if the pressed key is the designated toggle key. if (e.key.toLowerCase() === TOGGLE_KEY) { log(`Toggle key '${TOGGLE_KEY}' pressed.`); isEnabled = !isEnabled; // Flip the enabled state. GM_setValue('tgMediaEnhancerEnabled', isEnabled); // Save the new state. log(`Script is now ${isEnabled ? 'ENABLED' : 'DISABLED'}. Saved state.`); showNotification(`Media Enhancer: ${isEnabled ? 'ON' : 'OFF'}`); if (!isEnabled) { // If disabled, immediately turn off the fullscreen features. log("Script disabled, ensuring fullscreen is turned off."); disableFullscreen(); } else { // If enabled, reset processed media to force a re-evaluation on the next check. log("Script enabled, resetting processed media element to force re-evaluation."); processedMediaElement = null; } } }); // --- Initialization --- // Start the main loop. setInterval(checkViewState, CHECK_INTERVAL_MS); log('Script v2.1 loaded and checkViewState interval started.'); // Show a notification on load to inform the user that the script is active. showNotification(`Media Enhancer Loaded (Toggle key: G)`); })();