您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Opens a separate window for YouTube video playback on Tinychat.
// ==UserScript== // @name Tinychat YouTube Playback Script // @namespace tinychat-yt-playback // @description Opens a separate window for YouTube video playback on Tinychat. // @version 2.0 // @license CC0-1.0 // @author meth0dZ // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_xmlhttpRequest // @run-at document-end // @match https://tinychat.com/room/* // ==/UserScript== // Tinychat YouTube Playback - © [2023] [meth0dZ] // SPDX-License-Identifier: CC0-1.0 (async () => { 'use strict'; // Regular expression for matching YouTube URLs const videoRegex = /https?:\/\/(?:www\.)?youtu(?:be\.com|\.be)\/(?:watch\?v=|embed\/)([a-zA-Z0-9-_]+)/; // Function to fetch YouTube API key async function fetchAPIKey() { // Add your logic to fetch the API key from a secure location return 'ADD_YOUR_API_KEY'; } // Fetch and store the YouTube API key const apiKey = await fetchAPIKey(); // Function to open a separate window for video playback const openVideoWindow = (url) => { const videoWindow = window.open(url, 'tinychat-video', 'toolbar=0,location=0,status=0,menubar=0,resizable=1,width=640,height=360,autoplay=1'); videoWindow.focus(); } // Add CSS styles for the video button GM_addStyle(` .tc-video-button { display: inline-block; width: 32px; height: 32px; background-color: #ff0000; border-radius: 50%; opacity: 0.7; cursor: pointer; } .tc-video-button:hover { opacity: 1; } `); // Create a menu command to clear the stored video URL GM_registerMenuCommand('Clear Tinychat YouTube Video URL', () => { GM_setValue('tcYtVideoUrl', ''); }); // Check for a stored video URL and display the video button if found const videoUrl = GM_getValue('tcYtVideoUrl', ''); if (videoUrl !== '') { const videoButton = document.createElement('div'); videoButton.className = 'tc-video-button'; videoButton.title = 'Play YouTube Video'; videoButton.onclick = () => openVideoWindow(videoUrl); document.body.appendChild(videoButton); } // Function to create the YouTube search bar function createSearchBar() { // Create the search bar container const searchBar = document.createElement('div'); Object.assign(searchBar.style, { position: 'fixed', top: '10px', left: '10px', zIndex: '9999', }); // Create the search input field const searchInput = document.createElement('input'); searchInput.type = 'text'; searchInput.id = 'yt-search-input'; searchInput.placeholder = 'Search'; searchInput.setAttribute('autocomplete', 'off'); searchInput.setAttribute('list', 'yt-search-datalist'); searchBar.appendChild(searchInput); // Create the search datalist element const searchDatalist = document.createElement('datalist'); searchDatalist.id = 'yt-search-datalist'; searchBar.appendChild(searchDatalist); // Add event listener for search input updates searchInput.addEventListener('input', async () => { const query = searchInput.value.trim(); if (query.length < 3) { searchDatalist.innerHTML = ''; return; } // Build the YouTube search API request URL const searchUrl = `https://www.googleapis.com/youtube/v3/search?part=snippet&q=${encodeURIComponent(query)}&type=video&key=${apiKey}&maxResults=5`; // Fetch YouTube search results using GM_xmlhttpRequest GM_xmlhttpRequest({ method: 'GET', url: searchUrl, onload: (response) => { const { items } = JSON.parse(response.responseText); if (items?.length > 0) { searchDatalist.innerHTML = ''; // Clear existing options before adding new ones items.forEach(({ id: { videoId }, snippet: { title } }) => { addUniqueOption(searchDatalist, videoId, title); }); } }, onerror: () => { console.error('Error fetching search results'); } }); }); // Function to add an option to the datalist only if it's unique function addUniqueOption(datalist, videoId, title) { // Check if the option already exists in the datalist const existingOption = [...datalist.children].find(option => option.getAttribute('data-video-id') === videoId); if (!existingOption) { const option = document.createElement('option'); option.setAttribute('data-video-id', videoId); option.innerText = title; datalist.appendChild(option); } } // Create the search button const searchButton = document.createElement('button'); searchButton.innerText = 'Play Video'; searchButton.onclick = () => { const selectedOption = searchInput.value; const selectedVideoId = [...searchDatalist.children].find(option => option.innerText === selectedOption)?.getAttribute('data-video-id'); // Play the selected video and store the video URL if (selectedVideoId) { GM_setValue('tcYtVideoUrl', `https://www.youtube.com/embed/${selectedVideoId}?autoplay=1`); openVideoWindow(`https://www.youtube.com/embed/${selectedVideoId}?autoplay=1`); searchInput.value = ''; } else { alert('Invalid YouTube URL'); } }; searchBar.appendChild(searchButton); // Add the search bar to the document body document.body.appendChild(searchBar); } // Create the search bar createSearchBar(); })();