您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Перемотка видео стрелками и восстановление по автору и времени
// ==UserScript== // @name Telegram Web — перемотка + сохранение прогресса видео // @version 1.1 // @description Перемотка видео стрелками и восстановление по автору и времени // @match https://web.telegram.org/* // @grant none // @namespace https://greasyfork.org/users/789838 // @license MIT // ==/UserScript== (function() { 'use strict'; // --- Configuration --- const STORAGE_KEY = 'tg_video_progress'; const REWIND_TIME_SECONDS = 5; // Time to jump forward/backward on arrow key press const SAVE_PROGRESS_INTERVAL_MS = 2000; // How often to save video progress // --- Utility Functions --- /** * Loads video progress from localStorage. * @returns {Object} An object mapping video keys to their last known playback time. */ const loadProgress = () => { try { const data = localStorage.getItem(STORAGE_KEY); return data ? JSON.parse(data) : {}; } catch (e) { console.error('Telegram Video Progress: Error loading progress from localStorage:', e); return {}; } }; /** * Saves video progress to localStorage. * @param {Object} obj The object to save. */ const saveProgress = obj => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(obj)); } catch (e) { console.error('Telegram Video Progress: Error saving progress to localStorage:', e); } }; /** * Checks if an element is visibly rendered on the page. * @param {HTMLElement} el The element to check. * @returns {boolean} True if the element is visible, false otherwise. */ const isVisible = el => { return el && el.offsetParent !== null && el.offsetWidth > 0 && el.offsetHeight > 0 && window.getComputedStyle(el).visibility !== 'hidden' && window.getComputedStyle(el).display !== 'none'; // Added display check }; // --- Keyboard Event Handler for Rewind --- // Using a Set to keep track of active interval IDs for each video const activeIntervals = new Map(); document.addEventListener('keydown', e => { // Check if the focus is on an input field to prevent unintended rewinds if (document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')) { return; } if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') { // Find the currently active/visible video. // Prioritize videos within the media viewer if open, otherwise any visible video. let video = document.querySelector('.media-viewer-content video'); if (!video || !isVisible(video)) { const videos = Array.from(document.querySelectorAll('video')).filter(v => isVisible(v)); video = videos[0]; // Take the first visible video if media viewer is not active } if (video && !video.paused && !video.ended) { // Only rewind if video is playing const newTime = video.currentTime + (e.key === 'ArrowRight' ? REWIND_TIME_SECONDS : -REWIND_TIME_SECONDS); video.currentTime = Math.max(0, Math.min(newTime, video.duration || newTime)); // Clamp to valid range e.stopPropagation(); // Stop event propagation to prevent Telegram's default actions e.preventDefault(); // Prevent default browser action (e.g., scrolling) } } }, true); // Use `true` for capture phase to ensure it runs before Telegram's handlers // --- Media Viewer Observer --- const observer = new MutationObserver(mutations => { // Optimized to only re-evaluate when necessary const mediaViewer = document.querySelector('.media-viewer-modal, .media-viewer-backdrop'); // More robust selector for the viewer if (!mediaViewer || !isVisible(mediaViewer)) { // If media viewer is closed or not visible, clear all intervals activeIntervals.forEach(intervalId => clearInterval(intervalId)); activeIntervals.clear(); return; } const nameEl = mediaViewer.querySelector('.media-viewer-name .peer-title, .media-viewer-modal .ChannelInfo-title, .media-viewer-modal .PrivateChatInfo-title'); // Broader selection for titles const dateEl = mediaViewer.querySelector('.media-viewer-date, .media-viewer-modal .ChatInfo-date'); // Broader selection for dates const video = mediaViewer.querySelector('video'); if (!nameEl || !dateEl || !video || !isVisible(video)) { return; } const name = nameEl.textContent.trim(); const date = dateEl.textContent.trim(); // Use a combination of URL and date to make the key more unique for the same author on different dates const videoSrc = video.src || video.currentSrc; const key = `${name} @ ${date} ${videoSrc}`; let store = loadProgress(); // Restore progress // Use a unique dataset attribute or a WeakMap for tracking to avoid conflicts if (store[key] && !video.dataset.tgProgressRestored) { video.currentTime = store[key]; video.dataset.tgProgressRestored = 'true'; // Mark as restored console.log(`Telegram Video Progress: Restored progress for "${name}" from ${store[key].toFixed(2)}s`); } // Set up interval for saving progress // Ensure only one interval per video instance if (!activeIntervals.has(key)) { // Clear any existing interval for this key if it somehow lingered if (activeIntervals.has(key)) { clearInterval(activeIntervals.get(key)); } const intervalId = setInterval(() => { if (video.paused || video.ended || !isVisible(video)) { // Clear interval if video is paused, ended, or no longer visible clearInterval(intervalId); activeIntervals.delete(key); console.log(`Telegram Video Progress: Stopped saving progress for "${name}".`); return; } // Only save if progress changed meaningfully to avoid excessive writes if (Math.abs(store[key] - video.currentTime) > 1) { // Save if changed by more than 1 second store[key] = video.currentTime; saveProgress(store); // console.log(`Telegram Video Progress: Saved progress for "${name}" to ${video.currentTime.toFixed(2)}s`); } }, SAVE_PROGRESS_INTERVAL_MS); activeIntervals.set(key, intervalId); console.log(`Telegram Video Progress: Started saving progress for "${name}".`); } }); // Observe the document body for changes, particularly when the media viewer opens/closes observer.observe(document.body, { childList: true, subtree: true }); console.log('Telegram Video Progress: Script initialized.'); })();