ютьюб видео
// ==UserScript==
// @name @vid
// @namespace нету
// @version 0
// @description ютьюб видео
// @author жди
// @match *://lolz.live/*
// @match *://zelenka.guru/*
// @match *://lolz.guru/*
// @match *://lolz.market/*
// @match *://zelenka.market/*
// @match *://lzt.market/*
// @license MIT
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @connect www.youtube.com
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
const AUTOSEND_SETTING_KEY = "autosend_enabled";
let autoSendEnabled = GM_getValue(AUTOSEND_SETTING_KEY, false);
let lastQuery = '';
let debounceTimer = null;
let suppressObserver = false;
let cmdId = null;
init();
function initObserver() {
const observer = new MutationObserver(() => {
if (suppressObserver) return;
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const text = editor.innerText.trim();
if (!text.startsWith('@vid ')) {
removeResults();
lastQuery = '';
return;
}
const query = text.slice('@vid '.length).trim();
if (!query) {
removeResults();
lastQuery = '';
return;
}
if (query === lastQuery && document.querySelector('.vid-results-renderer')) {
return;
}
clearTimeout(debounceTimer);
debounceTimer = setTimeout(async () => {
const currentEditor = document.querySelector('.tiptap.ProseMirror');
if (!currentEditor) return;
const currentText = currentEditor.innerText.trim();
if (!currentText.startsWith('@vid ')) {
removeResults();
lastQuery = '';
return;
}
const currentQuery = currentText.slice('@vid '.length).trim();
if (!currentQuery) {
removeResults();
lastQuery = '';
return;
}
if (currentQuery === lastQuery && document.querySelector('.vid-results-renderer')) {
return;
}
try {
const videos = await getVideos(currentQuery);
lastQuery = currentQuery;
renderVideos(videos);
} catch (err) {
console.error('[vid] req failed:', err);
removeResults();
}
}, 400);
});
observer.observe(document.body, {
childList: true, subtree: true, characterData: true
});
}
function addStyles() {
GM_addStyle(`
.vid-results-renderer {
position: absolute;
left: 60px;
bottom: calc(100% + 8px);
z-index: 9999;
pointer-events: none;
}
.vid-popup {
pointer-events: auto;
display: flex;
flex-direction: column;
gap: 6px;
width: min(420px, calc(100vw - 80px));
max-height: 700px;
overflow-y: auto;
padding: 8px;
border-radius: 8px;
background: #2b2b2b;
border: 1px solid rgba(255,255,255,0.08);
box-shadow: 0 8px 24px rgba(0,0,0,0.35);
}
.vid-result {
width: 100%;
min-height: 64px;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
background: rgba(255,255,255,0.04);
display: flex;
align-items: center;
gap: 10px;
padding: 6px;
box-sizing: border-box;
}
.vid-result img {
width: 54px;
height: 54px;
min-width: 54px;
object-fit: cover;
display: block;
border-radius: 6px;
background: rgba(255,255,255,0.06);
}
.vid-info {
min-width: 0;
flex: 1;
}
.vid-title {
color: #fff;
font-size: 13px;
font-weight: 700;
line-height: 1.25;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.vid-views {
color: rgba(255,255,255,0.65);
font-size: 12px;
line-height: 1.25;
margin-top: 4px;
}
.vid-result:hover {
outline: 2px solid rgba(255,255,255,0.35);
}
`);
}
function registerSetting() {
if (cmdId !== null) {
GM_unregisterMenuCommand(cmdId);
}
const commandTitle = (autoSendEnabled ? "Выключить" : "Включить") + " автоотправку";
cmdId = GM_registerMenuCommand(commandTitle, () => {
autoSendEnabled = !autoSendEnabled;
GM_setValue(AUTOSEND_SETTING_KEY, autoSendEnabled);
registerSetting();
});
}
function removeResults() {
suppressObserver = true;
document.querySelectorAll('.vid-results-renderer').forEach(el => el.remove());
queueMicrotask(() => {
suppressObserver = false;
});
}
function renderVideos(videos) {
removeResults();
if (!videos || !videos.length) return;
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const wrapper = document.querySelector('.editor-box-wrapper') || editor.parentElement;
if (!wrapper) return;
if (getComputedStyle(wrapper).position === 'static') {
wrapper.style.position = 'relative';
}
const renderer = document.createElement('div');
renderer.className = 'vid-results-renderer';
const popup = document.createElement('div');
popup.className = 'vid-popup';
videos.forEach(src => {
const item = document.createElement('div');
item.className = 'vid-result';
const img = document.createElement('img');
img.src = src.thumbnail;
img.alt = src.title;
img.title = src.title;
const info = document.createElement('div');
info.className = 'vid-info';
const title = document.createElement('div');
title.className = 'vid-title';
title.textContent = src.title;
const views = document.createElement('div');
views.className = 'vid-views';
views.textContent = src.views;
info.appendChild(title);
info.appendChild(views);
item.appendChild(img);
item.appendChild(info);
item.addEventListener('click', () => {
insertVid(src.videoId);
removeResults();
});
popup.appendChild(item);
});
renderer.appendChild(popup);
suppressObserver = true;
wrapper.appendChild(renderer);
queueMicrotask(() => {
suppressObserver = false;
});
}
function insertVid(videoId) {
const editor = document.querySelector('.tiptap.ProseMirror');
if (!editor) return;
const text = editor.innerText;
const newText = text.replace(/^@vid(?:\s+.*)?$/m, `[MEDIA=youtube]${videoId}[/MEDIA]`);
editor.focus();
editor.innerText = newText;
editor.dispatchEvent(new Event('input', {bubbles: true}));
if (autoSendEnabled) setTimeout(() => unsafeWindow.$('[aria-label="send-message"]').trigger('click'), 200);
}
function request(url) {
return new Promise((resolve, reject) => {
toggleProgress('PseudoAjaxStart');
GM_xmlhttpRequest({
method: 'GET', url,
headers: {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
},
onload: res => {
toggleProgress('PseudoAjaxStop');
resolve(res.responseText);
},
onerror: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
},
ontimeout: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
},
onabort: err => {
toggleProgress('PseudoAjaxStop');
reject(err);
}
});
});
}
async function getVideos(query) {
const q = encodeURIComponent(query);
const searchUrl = `https://www.youtube.com/results?search_query=${q}`;
const html = await request(searchUrl);
const data = extractData(html);
return parse(data).slice(0, 50);
}
function extractData(html) {
const marker = 'var ytInitialData = ';
let start = html.indexOf(marker);
if (start === -1) {
const marker2 = 'ytInitialData = ';
start = html.indexOf(marker2);
if (start === -1) {
throw new Error("ytInitialData not found");
}
start += marker2.length;
} else {
start += marker.length;
}
let i = start;
let depth = 0;
let inString = false;
let stringQuote = '';
let escaped = false;
for (; i < html.length; i++) {
const ch = html[i];
if (inString) {
if (escaped) {
escaped = false;
} else if (ch === '\\') {
escaped = true;
} else if (ch === stringQuote) {
inString = false;
}
continue;
}
if (ch === '"' || ch === "'") {
inString = true;
stringQuote = ch;
continue;
}
if (ch === '{') depth++;
if (ch === '}') depth--;
if (depth === 0 && ch === '}') {
i++;
break;
}
}
return JSON.parse(html.slice(start, i));
}
function parse(data) {
const videos = [];
const seen = new Set();
walk(data, node => {
if (!node.videoRenderer) return;
const v = node.videoRenderer;
const videoId = v.videoId;
if (!videoId || seen.has(videoId)) return;
seen.add(videoId);
videos.push({
videoId,
title: getText(v.title),
views: formatViews(getText(v.viewCountText)),
thumbnail: getBestThumbnail(v.thumbnail?.thumbnails) || getThumbnail(videoId),
});
});
return videos;
}
function walk(obj, cb) {
if (!obj || typeof obj !== 'object') return;
cb(obj);
if (Array.isArray(obj)) {
obj.forEach(item => walk(item, cb));
} else {
Object.values(obj).forEach(value => walk(value, cb));
}
}
function getText(obj) {
if (!obj) return "";
if (obj.simpleText) return obj.simpleText;
if (Array.isArray(obj.runs)) return obj.runs.map(item => item.text).join("");
return "";
}
function getBestThumbnail(thumbnails) {
if (!Array.isArray(thumbnails) || !thumbnails.length) return "";
const best = thumbnails
.slice()
.sort((a, b) => (b.width || 0) - (a.width || 0))[0];
if (!best || !best.url) return "";
let url = best.url;
if (url.startsWith('//')) url = 'https:' + url;
if (url.startsWith('/')) url = 'https://www.youtube.com' + url;
return url;
}
function getThumbnail(videoId) {
return `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`;
}
function formatViews(text) {
const num = Number(String(text || '').replace(/[^\d]/g, ''));
if (!num) return text || '';
if (num >= 900000) {
return `${String(Math.floor(num / 100000) / 10).replace('.0', '')}M views`;
}
if (num >= 1000) {
return `${Math.floor(num / 1000)}K views`;
}
return `${num} views`;
}
function toggleProgress(eventName) {
const $ = unsafeWindow.jQuery || unsafeWindow.$;
if ($) {
$(unsafeWindow.document).trigger(eventName);
}
}
function init() {
initObserver();
addStyles();
registerSetting();
}
})();