add tampermonkey script
This commit is contained in:
37
README.md
37
README.md
@@ -0,0 +1,37 @@
|
||||
# Setup
|
||||
```
|
||||
python -m venv .venv
|
||||
```
|
||||
## Windows
|
||||
```
|
||||
.venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
ytmusicapi browser
|
||||
```
|
||||
## Linux
|
||||
```
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
ytmusicapi browser
|
||||
```
|
||||
|
||||
Add the script in [tampermonkey](tampermonkey) as new userscript in tampermonkey
|
||||
|
||||
|
||||
|
||||
# Usage
|
||||
```
|
||||
flask run
|
||||
```
|
||||
|
||||
```
|
||||
node server/server.cjs
|
||||
```
|
||||
|
||||
# Credits
|
||||
|
||||
Python code and javascript is written by me
|
||||
The bridge server was also written by me
|
||||
|
||||
CSS was generated by Gemini
|
||||
The tampermonkey script was also generated by Gemini since my first idea was to use the YTM-Desktop app as player until i realised that it doesn't support adding songs to the queue.
|
||||
|
||||
133
tampermonkey/script.js
Normal file
133
tampermonkey/script.js
Normal file
@@ -0,0 +1,133 @@
|
||||
// ==UserScript==
|
||||
// @name YTM Remote Control
|
||||
// @namespace http://tampermonkey.net/
|
||||
// @version 1.0
|
||||
// @description Remote control YTM via local server
|
||||
// @author Gemini
|
||||
// @match *://music.youtube.com/*
|
||||
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
|
||||
// @grant GM_xmlhttpRequest
|
||||
// @connect localhost
|
||||
// @run-at document-end
|
||||
// ==/UserScript==
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
console.log("Remote Script Running...");
|
||||
|
||||
function poll() {
|
||||
GM_xmlhttpRequest({
|
||||
method: "GET",
|
||||
url: "http://localhost:3000/poll",
|
||||
onload: (res) => {
|
||||
const data = JSON.parse(res.responseText);
|
||||
if (data && data.videoId && data.title && data.action && data.artist) {
|
||||
loadPage(data.title,data.artist,data.videoId,data.action);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadPage(title,artist,videoId,action) {
|
||||
|
||||
// 1. Find and open the search bar if it's closed
|
||||
const searchOpenButton = document.querySelector('ytmusic-search-box') ||
|
||||
document.querySelector('tp-yt-paper-icon-button[aria-label="Search"]');
|
||||
|
||||
if (searchOpenButton) {
|
||||
searchOpenButton.click();
|
||||
}
|
||||
|
||||
// 2. Wait a moment for the animation/DOM to catch up
|
||||
setTimeout(() => {
|
||||
const searchInput = document.querySelector('input.ytmusic-search-box') ||
|
||||
document.querySelector('#input.ytmusic-search-box') ||
|
||||
document.querySelector('input#input');
|
||||
|
||||
if (searchInput) {
|
||||
// 3. Focus and set the value
|
||||
searchInput.focus();
|
||||
searchInput.value = title + " " + artist;
|
||||
|
||||
// 4. Force the app to "see" the new text
|
||||
searchInput.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
searchInput.dispatchEvent(new Event('change', { bubbles: true }));
|
||||
|
||||
// 5. Instead of pressing Enter, find the "Search" icon inside the bar and click it
|
||||
// This is usually the glass icon that appears once you start typing
|
||||
const searchSubmitButton = document.querySelector('.search-icon.ytmusic-search-box') ||
|
||||
document.querySelector('ytmusic-search-box [icon="search"]');
|
||||
|
||||
if (searchSubmitButton) {
|
||||
searchSubmitButton.click();
|
||||
console.log("Search button clicked!");
|
||||
} else {
|
||||
// Fallback: If no button, try the "Enter" key again with a more complete event
|
||||
const opts = { bubbles: true, cancelable: true, key: 'Enter', code: 'Enter', keyCode: 13, which: 13 };
|
||||
searchInput.dispatchEvent(new KeyboardEvent('keydown', opts));
|
||||
searchInput.dispatchEvent(new KeyboardEvent('keypress', opts));
|
||||
searchInput.dispatchEvent(new KeyboardEvent('keyup', opts));
|
||||
console.log("Search button not found, tried full key sequence.");
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
setTimeout(() => {
|
||||
addToQueue(videoId, action);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function addToQueue(videoId, action) {
|
||||
console.log("adding");
|
||||
const actionLabel = action === 'next' ? 'play next' : 'add to queue';
|
||||
|
||||
function tryAddToQueue() {
|
||||
// 1. Find the link for your video
|
||||
const videoLink = document.querySelector(`a[href*="${videoId}"]`);
|
||||
|
||||
if (!videoLink) {
|
||||
console.log("Waiting for search results...");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Find the Action Menu button near that link
|
||||
let container = videoLink.parentElement;
|
||||
let menuButton = null;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
if (!container) break;
|
||||
menuButton = container.querySelector('button[aria-label="Action menu"]');
|
||||
if (menuButton) break;
|
||||
container = container.parentElement;
|
||||
}
|
||||
|
||||
if (menuButton) {
|
||||
menuButton.click();
|
||||
|
||||
// 3. Find the EXCLUSIVE "Add to queue" option
|
||||
setTimeout(() => {
|
||||
const menuOptions = document.querySelectorAll('ytmusic-menu-service-item-renderer');
|
||||
// We use a more strict filter to avoid "Play next"
|
||||
const addToQueue = Array.from(menuOptions).find(el => {
|
||||
const text = el.textContent.trim().toLowerCase();
|
||||
return text === actionLabel;
|
||||
});
|
||||
|
||||
if (addToQueue) {
|
||||
addToQueue.click();
|
||||
console.log("Success: Added to the end of the queue!");
|
||||
} else {
|
||||
console.error("Found the menu, but couldn't find the exact 'Add to queue' button.");
|
||||
}
|
||||
}, 400);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to run, and if search results aren't ready, try again in 1 second
|
||||
if (!tryAddToQueue()) {
|
||||
setTimeout(tryAddToQueue, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(poll, 1500);
|
||||
})();
|
||||
Reference in New Issue
Block a user