Greasy Fork is available in English.

Tinychat YouTube Playback Script

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();

})();