Greasy Fork is available in English.
started 2024-11-26
// ==UserScript==
// @name MDA youtube playlists
// @namespace http://tampermonkey.net/
// @version 2026.5.11.a
// @description started 2024-11-26
// @author mr-d-r
// @license MIT
// @match http*://*.youtube.com/*
// @include http*://*.youtube.com/*
// @exclude http*://accounts.youtube.com/*
// @exclude http*://www.google.*/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_addStyle
// @require https://update.greasyfork.org/scripts/524553/1821889/MDA%20library.js
// @require https://update.greasyfork.org/scripts/577495/1821180/Date%20Format%20123.js
// @require https://update.greasyfork.org/scripts/577494/1821178/MDA%20youtube%20common.js
// ==/UserScript==
// !!! супер полезно Improved youtube - bnomihfieiccainjcjblhegjgglakjdd\4.1350_0\js&css\web-accessible\www.youtube.com\playlist-complete-playlist.js
// можно пользоваться всеми функциями напрямую - ImprovedTube.playlistAttachQuickButtons()
// getPlaylistId, playlist_id ytd-playlist YouTube's edit_playlist API
// кнопка Save на нормальном месте !!! - https://www.youtube.com/watch?v=QxeyEiQXRK0 https://www.youtube.com/watch?v=12PIeXmSfsA
// как-то выковырять локализованное название кнопки save2playlist
// MORE_VERT <==> ccessibilityText: "Ещё" <==> button[aria-label="Ещё"]
// ytInitialData -> ... MenuButton playlistEditEndpoint -> {"listItemViewModel":{"title":{"content":"Добавить в плейлист"}
// "PAadd_to_playlist"
// ДООТЛАЖИВАТЬ на https://www.youtube.com/watch?v=_65uFu5Twlg !!! - кнопка Save стандартная см.также https://www.youtube.com/watch?v=7dcw1bi0YMc&t=3s
// срабатывает через раз и НЕ подсвечивает жирным !!!
// добавлять кнокни плейлистов И сразу под видео И справа от datetime !!! !!! !!!
// или оставить место справа от datetime только для моих сообщений
// строчка плейлистов _выше_ названия ролика - выглядит норм
// таблица при масштабировании не переносится на другие строчки пробовать делать таблицу из div-ов
/* var qqqqqqq=`<div class="divTable">
<div class="divTableBody">
<div class="divTableRow">
<div class="divTableCell"> </div>
<div class="divTableCell"> </div>
<div class="divTableCell"> </div>
<div class="divTableCell"> </div>
</div>
<div class="divTableRow">
<div class="divTableCell"> </div>
<div class="divTableCell"> </div>
<div class="divTableCell"> </div>
<div class="divTableCell"> </div>
</div>
</div>
</div>
<!-- DivTable.com -->`
var asasasasas=`
.divTable{
display: table;
width: 100%;
}
.divTableRow {
display: table-row;
}
.divTableHeading {
background-color: #EEE;
display: table-header-group;
}
.divTableCell, .divTableHead {
border: 1px solid #999999;
display: table-cell;
padding: 3px 10px;
}
.divTableHeading {
background-color: #EEE;
display: table-header-group;
font-weight: bold;
}
.divTableFoot {
background-color: #EEE;
display: table-footer-group;
font-weight: bold;
}
.divTableBody {
display: table-row-group;
}
`
*/
// пробовать перед кликом в save to playlist делать окно невидимым или временно УМЕШЬШАТЬ ЕГО РАЗМЕР ДО 0 !! чтобы не моргало !!!
// решить, как обновлять жирные, если внес в сохраненные через стандартное меню
// вешать onclick на стандартную кнопку сохранения !
// максимизировать стандартное окно со списком сохраненных плейлистов - закончить - донастроить прокрутку
// - <tp-yt-paper-dialog style-target="host" role="dialog" tabindex="-1" class="style-scope ytd-popup-container" prevent-autonav="true" style="outline: none; height: 800px; z-index: 2202; position: fixed; top: 0.0041629px; left: 201.479px; box-sizing: border-box; max-height: 774.004px; max-width: 659px;" modern=""><!--css-build:shady--><!--
// - сначала смотреть сколько высота окна в пикселях !!!
// #playlists.ytd-add-to-playlist-renderer>*.ytd-add-to-playlist-renderer:not(:last-child) { margin-bottom: -4px; <<<
// ytd-add-to-playlist-renderer[dialog] #playlists.ytd-add-to-playlist-renderer { max-height: 557px; <<<
// try min-height: 100%
// еще функцию, которая вытаскивает все плейлисты в массив !!
// округлые окантовки https://ru.stackoverflow.com/questions/120246/%D0%9A%D0%B0%D0%BA-%D1%81%D0%BA%D1%80%D1%83%D0%B3%D0%BB%D0%B8%D1%82%D1%8C-%D0%BA%D1%80%D0%B0%D1%8F-%D1%83-%D1%82%D0%B0%D0%B1%D0%BB%D0%B8%D1%86%D1%8B-border-radius
// - хранить мои ключи в файле D:\Work\gdrive_obs\myJavascript\youtube.pers.js
// - потом генерировать OAuth2 токен с минимальными правами - чтение playlists и добавление в playlist, чтобы в случае утечки никто ничего не похерил
// разместить кнопки плейлистов в строчке даты 22.09.2025, 09:39 под названием видео
// делать не стандартные синие ссылки на сохранёнки, а пытаться назначить стандартные ютюбовские yt-button - подбирать
/*
fetch(url, options)
.then(response => response.json())
.then(result => // обрабатываем результат )
*/
var scrpt="MDA playlists";
(function() {
'use strict';
// start of the script - MUST BE AT THE VERY BEGIN - right below 'use strict'; !!! иначе у гугла выполняется 3 раза несмотря на if( wlh.match(/accounts.google.com/) ) { return; } !!!
var dbg=0, wlh=WLH();
var scrpt="MDA playlists";
var t_id="mdaAJRTE", isSaveButtonClicked=false, SaveButtonMUTEX=false;
console.log("\n", "\n", `${GM.info.script.name}: start -> ${wlh} debug=${(typeof dbg == "undefined") ? "absent" : dbg} MDAlib=${(typeof MDAlib == "undefined") ? "absent" : MDAlib}` );
if (window.location.href.match("accounts.youtube")) { console.log("accounts.youtube is detected, exiting this instance..."); return null; }
// else console.log("normal youtube is detected");
//if(dbg) log("--> 1"); try1();
// //if(dbg) log("--> 2"); try2();
//if(dbg) log("--> 3"); tryOAUTH();
// //if(dbg) log("--> -end-");
var sc="YTinterop222:";
// ONLY ONCE!!!!: A top-level observer that waits for <ytd-popup-container> to show up, then calls handlePopupContainer() exactly once.
const documentObserver11 = new MutationObserver(function (mutations, me) {
if(SaveButtonMUTEX) return;
const popupContainer = query(document, 'tp-yt-iron-dropdown'); // pre-2025: 'tp-yt-paper-dialog')[0];
if (popupContainer) {
SaveButtonMUTEX=true;
ttout(2000, () =>{ SaveButtonMUTEX=false; }); // все равно срабатывает слишком часто !!!
/* пробовать для сокращения срабатываний рецепт *s123646 НИЖЕ
*/
console.log("2make later only once: Found ytd-popup-container via MUTEX");
mkCheckedBold("mutation");
me.disconnect(); // stop once found
}
});
function query(startNode, selector) {
try {
return Array.prototype.slice.call(startNode.querySelectorAll(selector));
} catch (e) {
return [];
}
}
documentObserver11.observe(document, { childList: true, subtree: true });
/* // задействовать и этот *s123646 !!!
// и там есть еще !!!
// When the dialog closes, re-attach the observer so we see the next open event.
function observePaperDialogClose(paperDialog, onCloseDialog) {
let ob = new MutationObserver((mutations, me) => {
mutations.forEach(mutation => {
if (mutation.type === 'attributes' && mutation.attributeName === 'aria-hidden') {
me.disconnect();
onCloseDialog();
}
});
});
ob.observe(paperDialog, { attributes: true });
}
*/
function closeChaptersInFullscreen () { var aa
// In this video | В этом видео - это от 'Improve YouTube!' 🎧 (for YouTube & Videos) !!!
if( aa=qS("#panels-full-bleed-container [target-id='engagement-panel-macro-markers-description-chapters'] #visibility-button button") ) {
aa.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.visibility='hidden';
aa.click();
logdbg("closeChaptersInFullscreen: closed1");
}
deleteElement("#panels-full-bleed-container"); // workaround 2
if( aa=qS("#header #title-container [aria-label='Key moments'], #header #title-container [aria-label='Ключевые моменты']") ) {
aa?.parentNode?.parentNode?.querySelector("#visibility-button button")?.click();
logdbg("closeChaptersInFullscreen: closed2");
}
} // closeChaptersInFullscreen()
window.matchMedia('(display-mode: fullscreen)').addListener(({ matches }) => { var aa;
if (matches) {
log("fullscreen !!! ", document.fullscreenElement=true); // Apply fullscreenmode mode related changes
closeChaptersInFullscreen();
ttout(100, ()=>{ closeChaptersInFullscreen() });
ttout(300, ()=>{ closeChaptersInFullscreen() });
ttout(1000, ()=>{ closeChaptersInFullscreen() });
ttout(2000, ()=>{ closeChaptersInFullscreen() });
} else {
log("fullscreen exit ", document.fullscreenElement=true); // Remove fullscreenmode mode related changes
if( aa=qS("#panels-full-bleed-container [target-id='engagement-panel-macro-markers-description-chapters'] #visibility-button button") ) { aa.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.visibility=''; }
if( aa=qS("#secondary [target-id='engagement-panel-macro-markers-description-chapters'] #visibility-button button") ) { aa.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode.style.visibility=''; }
}
});
async function try1 () { var fn='try1'
const channel = "UC6xr8g8bl6Muw5OdHebEpMw" // 'UCr1QVPhsNT9cMI8FXAbzlig' // 'UChOIeBWpgfnSYN9nk1Ns0Bw';
const API_KEY = "AIzaSyCqWLx-j3Woi1x9JqJ-xkDUALyDWu--rhI" // левый из интернета 'AIzaSyAbbXpzVK4jD_ILNRZLBhD89BSn3vu726k';
//const url = `https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=${channel}&maxResults=100&order=date&type=video&key=${API_KEY}`; // WORKS
// var url = `https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId=${channel}&maxResults=100&order=date&type=video&key=${API_KEY}`; // WORKS
var url = `https://www.googleapis.com/youtube/v3/playlists?part=snippet&channelId=${channel}&maxResults=100&order=date&type=video&key=${API_KEY}`;
(async()=> { const res = await fetch(url); const data = await res.json();
log(fn,"playlists data", data); log(fn, "data pageInfo: ", data.pageInfo); log(fn, "data items[0]: ", data.items[0]);
data.items.forEach( (it,idx,arr)=>{
log(fn,"playlist:", it.id, " - ", it.snippet.localized.title); // it.snippet.localized.title
});
})();
log(fn, "() END");
} // try1()
async function try2 () { var fn='try2'
const channel = "UC6xr8g8bl6Muw5OdHebEpMw" // 'UCr1QVPhsNT9cMI8FXAbzlig' // 'UChOIeBWpgfnSYN9nk1Ns0Bw';
const API_KEY = "AIzaSyCqWLx-j3Woi1x9JqJ-xkDUALyDWu--rhI" // левый из интернета 'AIzaSyAbbXpzVK4jD_ILNRZLBhD89BSn3vu726k';
var playlistId = 'PLtuD-NjJNBJzOAvsrlHfKBOGtEvPcGAM3' // 'PL_oXsSI8g_a-E4g_PmS5s5fRceVhUAPrW' //'PLVqpOPG2Lex-CL8kKFxsGSGiPZZbfiNsR' //'PL2Nc0vlZLqfnqPjZ_jc8r6iumGqTi8Zlu'; //'PL2Nc0vlZLqfnqDjRqKDewG3llbfO9MmpE'; // OK
// var url = `https://www.googleapis.com/youtube/v3/playlistItems?playlistId=${playlistId}&part=snippet&channelId=${channel}&maxResults=200&order=date&type=video&key=${API_KEY}`; // WORKS
var url = `https://www.googleapis.com/youtube/v3/playlistItems?playlistId=${playlistId}&part=snippet&maxResults=200&order=date&type=video&key=${API_KEY}`;
(async()=> {
const res = await fetch(url);
const data = await res.json();
log(fn, "playlists data", data); // WORKS
log(fn, "data pageInfo: ", data.pageInfo);
log(fn, "data items[0]: ", data.items[0]);
data.items.forEach( (it,idx,arr)=>{
log(fn,"-- idx:",idx, " - ", it.snippet.resourceId.videoId, " - ", it.snippet.title); // it.snippet.resourceId.videoId, " - ", it.title);
});
})();
log(fn, "() END");
} // try2()
async function tryOAUTH () { var fn='tryOAUTH';
const channel = "UC6xr8g8bl6Muw5OdHebEpMw" // вроде мой mdasysadm
const API_KEY = "AIzaSyCqWLx-j3Woi1x9JqJ-xkDUALyDWu--rhI" // mda api key
var playlistId = 'PLtuD-NjJNBJzOJrUxDBW5NngXpIbNwpWj'; // publicTEST playlist
var OAUTH2_CLIENT_ID = '17933679225-shdikvhk0de8goa52up3jd49qgsp6s8u.apps.googleusercontent.com'; // my real clientid
var OAUTH2_SCOPES = [ 'https://www.googleapis.com/auth/youtube' ];
// заполнить аккуратно поля тут https://developers.google.com/youtube/v3/docs/playlists/list#properties
// https://developers.google.com/youtube/v3/guides/auth/client-side-web-apps
// https://www.googleapis.com/auth/youtube.force-ssl
// JavaScript sample code
// function oauthSignIn()
} // try2()
function crTable () { var pEl, htm, a, b, t, anch;
var st=`cursor: pointer; color: navy; text-align: center;
border-radius: 10px; -webkit-border-radius: 10px; -moz-border-radius: 10px; -khtml-border-radius: 10px; border: 1px solid #000; padding-left: 2px; padding-right: 5px; `; // display: block;
if( typeof MDAytlib == "undefined" ) { log("1122947652 no youtube lib included - exiting"); return; }
pEl=qS("#above-the-fold > #title");
if (!pEl) { if(dbg) log("2745632 parent #above-the-fold > #title is NOT FOUND", pEl); return; }
// a=qS("#mdaDateTimeSavelist_tbl");
a=qS("#mdaSavelist_tbl");
// if (!a) { b=pEl.appendChild( fromHTML123(mdaSavelistTBL) ); log("2745632 NEW TABLE --- ", b); }
if (!a) { pEl.insertAdjacentHTML('afterbegin', mdaSavelistTBL); }
if( t=qS(`#${t_id}_dummy12133`) ) { log("--- already created", t); return; }
// if( anch=qS("#mdaDateTime_row1") ) {
if( anch=qS("#mdaDateTime_row") ) {
a=qS(`#${t_id}_dummy12133`); // doublecheck
htm = ` <td style="${st}" id="${t_id}_беларусь" title="Беларусь"> BY </td>
<td style="${st}" id="${t_id}_польша" title="Польша"> PL </td>
<td style="${st}" id="${t_id}_украина" title="Украина"> UA </td>
<td style="${st}" id="${t_id}_англ." title="Английский"> EN </td>
<td style="${st}" id="${t_id}_политика" title="Политика"> политика </td>
<td style="${st}" id="${t_id}_история" title="История"> история </td>
<td style="${st}" id="${t_id}_старое - ностальгия" title="Ностальгия"> ностальгия </td>
<td style="${st}" id="${t_id}_юмор" title="Юмор"> 😂 </td>
<td style="${st}" id="${t_id}_интересное" title="Интересное"> цікава </td>
<td style="${st}" id="${t_id}_travel" title="Путешествия"> ✈ 🌴 </td>
<td style="${st}" id="${t_id}_котики" title="Котики"> 😹🐈 </td>
<td style="${st}" id="${t_id}_кулинария" title="Кулинария"> 🍽 cook 🍕 </td>
<td style="${st}" id="${t_id}_films" title="Фильмы"> 🎞 </td>
<td style="${st}" id="${t_id}_музыка" title="Музыка"> 🎝 </td>
<td style="${st}" id="${t_id}_аудиокниги" title="Аудиокниги"> 🔉 🕮 </td>
<td style="${st}" id="${t_id}_lifehacks" title="Лайфхаки"> 🛠 ⚙ </td>
<td style="${st}" id="${t_id}_здоровье" title="Здоровье"> ⚕ 💊 </td>
<td id="${t_id}_dummy12133"></td>
`
if( !a ) { anch.insertAdjacentHTML('beforeend', htm); log('---crTable() new: ', qS(`#${t_id}_11`) );
qS(`#${t_id}_беларусь`)?.addEventListener("click", onclick110); function onclick110() { save2playlist('беларусь'); }
qS(`#${t_id}_украина`)?.addEventListener("click", onclick111); function onclick111() { save2playlist('украина'); }
// go to playlist on dblclick !!! // https://www.youtube.com/watch?v=&list=PLtuD-NjJNBJyA3WcjDd8zxL6dXwcGRTkQ - пока нигде в html не нашел
qS(`#${t_id}_украина`)?.addEventListener("dblclick", onDBLclick111); function onDBLclick111() { // make direct jump to playlist: https://www.youtube.com/playlist?list=PLtuD-NjJNBJzOJrUxDBW5NngXpIbNwpWj
alert('double click on украина'); }
qS(`#${t_id}_политика`)?.addEventListener( "click", onclick112); function onclick112() { save2playlist('политика'); }
qS(`#${t_id}_интересное`)?.addEventListener("click", onclick113); function onclick113() { save2playlist('интересное'); }
qS(`#${t_id}_котики`)?.addEventListener( "click", onclick114); function onclick114() { save2playlist('котики'); }
}
}
if( anch=qS("#mdaDateTime_row") ) { // #mdaDateTime_row2
//htm = ` <td style="${st}" id="${t_id}_англ"> англ </td>
// <td style="${st}" id="${t_id}_история"> история </td>
// <td style="${st}" id="${t_id}_аудиокниги"> аудиокниги </td>`
//anch.insertAdjacentHTML('beforeend', htm); log('---crTable() new: ', qS(`#${t_id}_21`) );
// CSS.escape() !!!
qS(`#${t_id}_${CSS.escape("англ.")}`)?.addEventListener("click", onclick211);function onclick211() { save2playlist('англ.'); } // css.escape надо, т.к. "англ." содержит точку
qS(`#${t_id}_история`)?.addEventListener( "click", onclick212); function onclick212() { save2playlist('история'); }
qS(`#${t_id}_аудиокниги`)?.addEventListener( "click", onclick213); function onclick213() { save2playlist('аудиокниги'); }
qS(`#${t_id}_films`)?.addEventListener( "click", onclick214); function onclick214() { save2playlist('films'); }
qS(`#${t_id}_музыка`)?.addEventListener( "click", onclick241); function onclick241() { save2playlist('музыка'); }
qS(`#${t_id}_здоровье`)?.addEventListener( "click", onclick217); function onclick217() { save2playlist('здоровье'); }
qS(`#${t_id}_юмор`)?.addEventListener( "click", onclick215); function onclick215() { save2playlist('юмор'); }
qS(`#${t_id}_lifehacks`)?.addEventListener( "click", onclick231); function onclick231() { save2playlist('lifehacks'); }
qS(`#${t_id}_кулинария`)?.addEventListener( "click", onclick232); function onclick232() { save2playlist('кулинария'); }
qS(`#${t_id}_польша`)?.addEventListener( "click", onclick233); function onclick233() { save2playlist('польша'); }
qS(`#${t_id}_${CSS.escape("старое - ностальгия")}`)?.addEventListener("click", onclick216); function onclick216() { save2playlist('старое - ностальгия'); }
}
} // crTable()
function mkCheckedBold (caller) { var pEl, a, b, t, anch, ab, accum='', fn="mkCheckedBold():";
if( ! wlh.match("youtube.*\/watch") ) { loginfo(`${fn} URL is not /watch/ - exiting...`); return; }
hide_Save2playlistDialog(true); clickSaveButton(); // hide then click
logwarn(`${fn} 48fg45: ${caller} `);
a=qS("#mdaDateTimeSavelist_tbl");
if (!a) { log("23434632 mkCheckedBold() no table", b); return; }
ab=qSA("tp-yt-iron-dropdown .ytContextualSheetLayoutContentContainer .ytListViewModelHost[role='list'] .ytListItemViewModelHost");
//logwarn(`chkbold: found: ${ab.length} >>> ${ab}`);
// НАДО сначала открыть-закрыть save, а потом проверять !!!
// делать флажок, чтобы функция отрабатывала полностью !!!
qSA(`[id^=${t_id}_]`)?.forEach(el=>{ a=el.id.replace(`${t_id}_`,''); // log("el23423: ", a); // для всех моих ярлыков
ab?.forEach(it=>{ //log(">>", it.getAttribute("aria-label") );
let rege = new RegExp(`^${a},`, 'gi'); // от начала строки до запятой !!!
if( it.getAttribute("aria-label").match(rege) ) { // было it.getAttribute("aria-label").match(а) но это кривова-то
b=it?.getAttribute("aria-pressed"); //logwarn(`chkbold: ${a} ${b}`);
if( b=='true' ) el.style.fontWeight="bold", el.style.fontColor="red", accum=accum +" " +a, el.style.backgroundColor='cyan'; // 2 debug more
else el.style.fontWeight="normal", el.style.fontColor="blue", el.style.backgroundColor='white'; // try gray background !!!!
// может стоит добавлять '>' к названию плейлиста ?!?!
}
});
});
//if(accum) showmsg_mdaDateTime_inf1(`found in ${accum}`, 3000);
ttout(1000, ()=> { closeSaveButton(); hide_Save2playlistDialog(false); });
/* pre-2025 way:
qSA(`[id^=${t_id}_]`)?.forEach(el=>{ a=el.id.replace(`${t_id}_`,''); // log("el: ", a);
b=qS("ytd-add-to-playlist-renderer")?.querySelector(`#checkbox-label.ytd-playlist-add-to-option-renderer > #label[title='${a}']`)?.parentNode?.parentNode?.parentNode?.parentNode?.ariaChecked;
//log("checked: ", b);
if( b=='true' ) el.style.fontWeight="bold";
else el.style.fontWeight="normal";
});
*/
} // mkCheckedBold()
function clickSaveButton () { var aa, bb;
// ждать пока сделается drag ?
if( aa=qS("#mdaSave") ) { // нашли кнопку там, куда я ее перетащил
///////////////if(ytSavePlaylistButtonAlreadyPressed) { logdbg(`clickSaveButton(): SavePlaylistButtonAlreadyPressed=true` ); return; }
// hide_Save2playlistDialog(true);
aa?.click(); log("savebutton clicked");
isSaveButtonClicked=true;
// после клика ждать пока не появится в DOM
// зарядить несколько ttout c проверкой, что появилась в DOM
ttout(3000, ()=>{ // hide_Save2playlistDialog(false);
ttout(1000, ()=> { save2playlistResize(); });
}); // make savelist visible again
}
} // clickSaveButton()
function closeSaveButton () { var aa;
// ДООТЛАЖИВАТЬ !!!
//log("closeSaveButton() obsoleted. return");
//return;
//clickSaveButton(); // to close
cl1212();
ttout(500, ()=>{ cl1212(); log("savebutton closed 1"); });
ttout(1000, ()=>{ cl1212();; log("savebutton closed 2"); });
function cl1212 () { document.querySelector("#mdaSave")?.dispatchEvent( new KeyboardEvent("keydown", { key: "Escape" }) ); }
} // closeSaveButton()
function save2playlist (plname) { var aa, ab, bb, tto=300;
log(`save2playlist(${plname}) started`);
if( !plname ) return;
//if( !isSaveButtonClicked) { clickSaveButton(); tto=1500; } // устарело, т.к. современная реализация на youtube ТРЕБУЕТ всегда кликать save button
hide_Save2playlistDialog(true);
ttout(tto, ()=> { var aa, ab, ccc;
// ab=qS("ytd-add-to-playlist-renderer")?.querySelectorAll("#checkbox-label.ytd-playlist-add-to-option-renderer > #label"); // pre-2025 way
ab=qSA("tp-yt-iron-dropdown .ytContextualSheetLayoutContentContainer .ytListViewModelHost[role='list'] .ytListItemViewModelHost");
log("dropdown found: ", ab ? "ok" : null);
ab?.forEach(el=>{
if( el.getAttribute("aria-label").match(plname) ) { log(`--1 ${plname} found`, "\nchcked: ", el.getAttribute("aria-pressed") );
// fails to set custom attribute el.setAttribute("mda_plName", plname);
el.click();
ccc=CSS.escape(`mdaAJRTE_${plname}`);
if( aa=qS(`#mdaSavelist_tbl #${ccc}`) ) aa.style.backgroundColor="plum"; // tmp paint in some tmp color to show the button clicked
ttout(2000, ()=>{ clickSaveButton(); mkCheckedBold("save2playlist(): try1 2sec"); });
ttout(4000, ()=>{ clickSaveButton(); mkCheckedBold("save2playlist(): try2 4sec"); });
ttout(7000, ()=>{ clickSaveButton(); mkCheckedBold("save2playlist(): try3 7sec"); });
//}
} //else { log(`--1else ${plname} found`, "\nchcked: ", el.parentNode.parentNode.parentNode.parentNode.ariaChecked); }
});
// new way:
ab=qS("tp-yt-iron-dropdown .ytContextualSheetLayoutContentContainer")?.querySelectorAll(`[aria-label^='${plname},']`);
log("--2 ab new way plname found? ", ab ? "ok" : null);
// document.querySelector("tp-yt-iron-dropdown .ytContextualSheetLayoutContentContainer [aria-label^='films,']")?.getAttribute("aria-pressed")
// НО эта гребаная блядота не обновляет флаг сразу при нажатии на пункт, т.е. надо переоткрывать меню save to playlist повторно !!!
closeSaveButton();
hide_Save2playlistDialog(false);
});
} // save2playlist()
document.addEventListener("keydown", (e) => {
var j, set=false, aa, bb, za, np="";
if( anyActiveInput() ) return; //if ((aaBB = document.activeElement) && (editable(a) || (a.tagName === "INPUT") || (a.tagName === "TEXTAREA"))) return;
switch (e.code) {
// case "KeyS": // keyS тут не срабатывает, т.к. скорее всего рубится в другом скрипте
case "KeyD":
// no need to run mkCheckedBold() for case "KeyV": case "ArrowLeft": case "ArrowRight":
if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) break;
mkCheckedBold(`keydown ${e.code} :37fh4742s:`);
// no need e.preventDefault();
break;
}
}); // addEventListener("keydown"...
window.addEventListener('load', function() { var aa;
console.log(`${GM.info.script.name} ===> on load event`);
TrustedHTMLworkaround2();
//func1(sc, "immed");
//func2(sc, "immed");
//ttout(10, ()=>{ func2(sc, "10 ms delay"); });
//ttout(700, ()=>{ func3(sc, "700 ms delay UPD"); });
//ttout(1000, ()=>{
if( wlh.match("youtube.*\/watch") || wlh.match("youtube.*\/shorts") ) {
log("ThreeDotsMenuClick to test only - remove later");
ThreeDotsMenuClick();
Save2playlistClick(); // глюк - пропадает полоса с названием-лайками-дизлайками
}
//});
ttout(2000, ()=> { save2playlistResize(); });
ttout(7000, ()=> { save2playlistResize(); });
ttout(1500, ()=> { crTable(); if( !isSaveButtonClicked) { clickSaveButton(); ttout(1500, ()=> { closeSaveButton(); mkCheckedBold("onLoad 1"); }); }
});
ttout(3000, ()=> { crTable(); if( !isSaveButtonClicked) { clickSaveButton(); ttout(1500, ()=> { closeSaveButton(); mkCheckedBold("onLoad 2"); }); }
if( wlh.match("youtube.*\/watch") || wlh.match("youtube.*\/shorts") ) {
ThreeDotsMenuClick(); ThreeDotsMenuClose(); log("ThreeDotsMenuClick delayed 3 sec - 2 test only")
}
if( aa=qS("#primary #below ytd-watch-metadata") ) aa.style.marginTop='4px'; // уменьшаем верт.белое поле между плеером и моей таблицей плейлистов 1/2
if( aa=qS("#primary #below #clarify-box") ) aa.style.marginTop='4px'; // уменьшаем верт.белое поле между плеером и моей таблицей плейлистов 2/2
});
ttout(7000, ()=> { mkCheckedBold("onLoad 3"); });
ttout(10000, ()=> { mkCheckedBold("onLoad 4"); });
}); // addEventListener('load'
document.addEventListener("visibilitychange", (event) => {
if (document.visibilityState == "visible") { mkCheckedBold("aasdqqwwe11753 onVisibilitychange"); log("aasdqqwwe11753"); crTable();
} //else { console.log("tab is inactive"); }
});
})(); // END
/*
(async () => {
const url = "https://example.org/products.json";
log("try1");
try {
log("try2");
const response = await fetch(url);
if (!response.ok) {
throw new Error(`try2 Response status: ${response.status}`);
}
const result = await response.json();
log("try3");
console.log("try3", result);
}
catch (error) {
console.error("try3", error.message);
}
})()
*/