Displays the BPM of a song on YouTube using Spotify API
当前为
// ==UserScript==
// @name YouTube BPM Display
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Displays the BPM of a song on YouTube using Spotify API
// @author Sergi0
// @match https://www.youtube.com/*
// @grant GM_xmlhttpRequest
// @connect api.spotify.com
// @icon https://www.freeiconspng.com/uploads/youtube-icon-app-logo-png-9.png
// // @license MIT
// @homepageURL https://greasyfork.org/es/scripts/511311
// ==/UserScript==
(function() {
'use strict';
// 1. Spotify API credentials
const CLIENT_ID = 'YOUR_SPOTIFY_API_CLIENT_ID';
const CLIENT_SECRET = 'YOUR_SPOTIFY_API_CLIENT_SECRET';
let accessToken = '';
let lastTitle = '';
let lastUrl = '';
// 2. Function to get Spotify access token
function getAccessToken(callback) {
if (CLIENT_ID.includes("YOUR_SPOTIFY") || CLIENT_SECRET.includes("YOUR_SPOTIFY")) {
displayMessage("Please provide your Spotify API keys to use this script.");
return;
}
const authString = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
GM_xmlhttpRequest({
method: 'POST',
url: 'https://accounts.spotify.com/api/token',
headers: {
'Authorization': `Basic ${authString}`,
'Content-Type': 'application/x-www-form-urlencoded'
},
data: 'grant_type=client_credentials',
onload: function(response) {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
accessToken = data.access_token;
callback();
} else {
displayMessage("Invalid Spotify API keys. Please provide valid keys.");
}
}
});
}
// 3. Function to search track on Spotify
function searchTrack(trackName, callback) {
const query = encodeURIComponent(trackName);
const apiUrl = `https://api.spotify.com/v1/search?q=${query}&type=track&limit=1`;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
headers: {
'Authorization': `Bearer ${accessToken}`
},
onload: function(response) {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
if (data.tracks.items.length > 0) {
const track = data.tracks.items[0];
callback(track);
} else {
displayMessage("No track found.");
}
} else {
displayMessage("Error in search request.");
}
}
});
}
// 4. Function to get track features (including BPM)
function getTrackFeatures(trackId, callback) {
const apiUrl = `https://api.spotify.com/v1/audio-features/${trackId}`;
GM_xmlhttpRequest({
method: 'GET',
url: apiUrl,
headers: {
'Authorization': `Bearer ${accessToken}`
},
onload: function(response) {
if (response.status === 200) {
const data = JSON.parse(response.responseText);
callback(data.tempo);
} else {
displayMessage("Error fetching track features.");
}
}
});
}
// 5. Function to display BPM and track information
function displaySongInfo(bpm, track) {
let existingElement = document.getElementById('song_info');
if (existingElement) {
existingElement.remove();
}
let songInfoElement = document.createElement('div');
songInfoElement.id = 'song_info';
songInfoElement.style.position = 'absolute';
songInfoElement.style.top = '10px';
songInfoElement.style.right = '10px';
songInfoElement.style.backgroundColor = '#ff0000';
songInfoElement.style.color = 'white';
songInfoElement.style.padding = '15px';
songInfoElement.style.borderRadius = '5px';
songInfoElement.style.fontWeight = 'bold';
songInfoElement.style.fontSize = '20px';
songInfoElement.style.textAlign = 'center';
let bpmElement = document.createElement('div');
bpmElement.id = 'bpm';
bpmElement.style.fontSize = '60px';
bpmElement.innerText = `BPM: ${bpm ? bpm.toFixed(2) : 'Not available'}`;
let artistSongElement = document.createElement('div');
artistSongElement.id = 'artist_song';
artistSongElement.style.fontSize = '20px';
artistSongElement.innerText = `${track.artists[0].name} / ${track.name}`;
songInfoElement.appendChild(bpmElement);
songInfoElement.appendChild(artistSongElement);
const playerElement = document.querySelector('#player');
if (playerElement) {
playerElement.appendChild(songInfoElement);
} else {
displayMessage("Could not find the player element to add BPM.");
}
}
// 6. Function to display messages
function displayMessage(message) {
let existingElement = document.getElementById('message_display');
if (existingElement) {
existingElement.remove();
}
let messageElement = document.createElement('div');
messageElement.id = 'message_display';
messageElement.style.position = 'fixed';
messageElement.style.top = '0';
messageElement.style.left = '0';
messageElement.style.width = '100%';
messageElement.style.backgroundColor = '#ff0000';
messageElement.style.color = 'white';
messageElement.style.padding = '10px';
messageElement.style.fontWeight = 'bold';
messageElement.style.fontSize = '20px';
messageElement.style.textAlign = 'center';
messageElement.style.zIndex = '9999';
messageElement.innerText = message;
document.body.appendChild(messageElement);
}
// 7. Main function
function main() {
const videoTitleElement = document.querySelector('#title h1');
if (videoTitleElement) {
const titleText = videoTitleElement.innerText.trim();
if (titleText !== lastTitle) {
lastTitle = titleText;
searchTrack(titleText, function(track) {
if (track) {
getTrackFeatures(track.id, function(bpm) {
displaySongInfo(bpm, track);
});
} else {
displayMessage("No track found.");
}
});
}
}
}
// 8. Function to wait for the video title indefinitely
function waitForTitle() {
const interval = setInterval(() => {
const videoTitleElement = document.querySelector('#title h1');
if (videoTitleElement) {
clearInterval(interval);
main();
}
}, 1000);
}
// 9. Observe changes in URL
function observeUrlChanges() {
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
waitForTitle();
}
});
urlObserver.observe(document.body, { childList: true, subtree: true });
}
// 10. Observe changes in title
function observeTitleChanges() {
const titleElement = document.querySelector('#title');
if (titleElement) {
const titleObserver = new MutationObserver(main);
titleObserver.observe(titleElement, {
childList: true,
subtree: true
});
}
}
// 11. Load script
window.addEventListener('load', () => {
getAccessToken(function() {
observeUrlChanges();
observeTitleChanges();
waitForTitle();
});
});
})();