Skip to content

Instantly share code, notes, and snippets.

@WiBla
Last active August 11, 2022 15:21
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save WiBla/ad1aa9a98949c624cd2886c1a25b5feb to your computer and use it in GitHub Desktop.
Save WiBla/ad1aa9a98949c624cd2886c1a25b5feb to your computer and use it in GitHub Desktop.
Add YouTube videos to plug.dj without having to search them.

What is this?

It's a script (think of it as mod) for plug.dj which will allow you to add YT (YouTube) videos to your playlists with one button.

Why is this necessary?

Plug.dj is having some trouble with their YT API Key (the thing that allows you to make YT related actions throughout the site, like searching for a song and adding the results to your playlists). Using this script, you will be able to add YT videos to your playlists again, even while plug.dj is dealing with YouTube's support to get this issue fixed.

How to use

After installing it, you should see a [+] button appearing in all your playlists. Go to the one you want to add a video, click it, and paste a YouTube video's link in the prompt that shows up. Click OK or press ENTER, and it should say Media added!, if not, the provided API key might already be used up for the day.

Gif showing how to use the script once installed If this gif isn't playing, click here: https://i.imgur.com/f1PD8eN.gifv

Install

Check this video made by STXPH which explains very-well how to install this script: https://youtu.be/IJFgl8BoxYs

How do I get my own API Key and why do I need one?

You will most likely have a better experience with this script if you get your own key, this is because the key I included is shared with everyone loading it. Since (free) API Keys are limited to 10 thousands quota points per day, if enough people are using it on a daily basis, it can quickly become useless. Having your own means you get more out of it, and you'll have to go pretty hard at it to reach your quota.

Check STXPH's video on how to generate an API key here: https://youtu.be/_hivX4e0S68

Known bugs

  • If you make a search using the input on top of your playlists, or filter them, the button will disappear.
    • [NEW] use the /grab-btn command to re-add the button!
  • Adding a song does not show up immediately
    • This is pretty hard to achieve and I don't have the time to do this, just switch playlists and it should pop up
  • The playlist media count isn't going up
    • Again, it's a small bug and I don't have the time/motivation to fix it, just refresh the page to see the real count.
// ==UserScript==
// @name Plug.dj YT API Key workaround
// @namespace https://plug.dj?refuid=4613422
// @version 1.7.0
// @author WiBla (contact.wibla@gmail.com)
// @description This script will add a button next to "import/create playlist" that allows you to add videos without searching for them
// @downloadURL https://gist.github.com/WiBla/ad1aa9a98949c624cd2886c1a25b5feb/raw/8d83fc7bb1ac77f8ff9494023991e21e5599ec56/yt-workaround.user.js
// @updateURL https://gist.github.com/WiBla/ad1aa9a98949c624cd2886c1a25b5feb/raw/8d83fc7bb1ac77f8ff9494023991e21e5599ec56/yt-workaround.user.js
// @include *://plug.dj/*
// @include *://*.plug.dj/*
// @exclude *://*.plug.dj/_/*
// @exclude *://*.plug.dj/@/*
// @exclude *://*.plug.dj/!/*
// @exclude *://*.plug.dj/about
// @exclude *://*.plug.dj/ba
// @exclude *://*.plug.dj/forgot-password
// @exclude *://*.plug.dj/founders
// @exclude *://*.plug.dj/giftsub/*
// @exclude *://*.plug.dj/jobs
// @exclude *://*.plug.dj/legal
// @exclude *://*.plug.dj/merch
// @exclude *://*.plug.dj/partners
// @exclude *://*.plug.dj/plot
// @exclude *://*.plug.dj/privacy
// @exclude *://*.plug.dj/purchase
// @exclude *://*.plug.dj/subscribe
// @exclude *://*.plug.dj/team
// @exclude *://*.plug.dj/terms
// @exclude *://*.plug.dj/press
// @grant none
// @run-at document-end
// ==/UserScript==
/* global $, gapi, API, _, require */
(function() {
'use strict';
// Because plug.dj hides the interface while loading, this is necessary
// We can't just use document.readyState ($.ready)
function plugReady() {
return typeof API !== 'undefined' && API.enabled && typeof jQuery !== 'undefined' && typeof require !== 'undefined';
}
function autoReload() {
if (!plugReady()) {
setTimeout(autoReload, 200);
} else {
init();
}
}
function init() {
var pl = {}, media = {}, APIKey = localStorage.getItem('yt-api-key');
if (APIKey == null) {
APIKey = 'AIzaSyD--___tekD3NI_-Sj8cAnNyuDKFmdtOkM';
API.chatLog('⚠ YT-workaround is using the default API key, this is not recommended as everyone uses the same and it is very limited. If you know how, you should definitely get your own and configure it with /key [YOUR KEY HERE]');
} else {
API.chatLog('YT-workaround is using a custom key, you rock 💪');
}
window.gapi.client.setApiKey(APIKey);
function convert_time(duration) {
var a = duration.match(/\d+/g),
indexOfH = duration.indexOf('H'),
indexOfM = duration.indexOf('M'),
indexOfS = duration.indexOf('S');
switch(true) {
case indexOfM >= 0 && indexOfH == -1 && indexOfS == -1:
a = [0, a[0], 0];
break;
case indexOfH >= 0 && indexOfM == -1:
a = [a[0], 0, a[1]];
break;
case indexOfH >= 0 && indexOfM == -1 && indexOfS == -1:
a = [a[0], 0, 0];
break;
}
duration = 0;
switch(a.length) {
case 1:
duration = duration + parseInt(a[0]);
break;
case 2:
duration = duration + parseInt(a[0]) * 60;
duration = duration + parseInt(a[1]);
break;
case 3:
duration = duration + parseInt(a[0]) * 3600;
duration = duration + parseInt(a[1]) * 60;
duration = duration + parseInt(a[2]);
break;
}
return duration;
}
function completeMedia() {
$.ajax({
url: `https://www.googleapis.com/youtube/v3/videos?id=${media.cid}&key=${gapi.config.get().client.apiKey}&part=snippet,contentDetails`,
type: 'GET',
success: (data) => {
media.author = data.items[0].snippet.title.split('-');
// Video's title contains a "-" so parse it
if (media.author.length > 1) {
media.title = media.author[1].trim();
media.author = media.author[0].trim();
} else { // Otherwise, use the channel's name as author..
media.title = media.author[0].trim();
media.author = data.items[0].snippet.channelTitle;
}
media.image = data.items[0].snippet.thumbnails.default.url;
media.duration = convert_time(data.items[0].contentDetails.duration);
media.format = 1;
media.id = -1;
addMedia();
},
error: (err) => {
if (err.responseJSON.error.message.indexOf('Daily Limit') > -1) {
alert('The current API Key has exceeded its quota.\nTry setting your own by typing "/key [YOUR KEY HERE]" in the chat');
} else {
console.error('[YT-WORKAROUND]', err);
alert("Something went wrong with YouTube. You can check the console for more info.");
}
}
});
}
function addMedia() {
$.ajax({
url: `/_/playlists/${pl.id}/media/insert`,
type: 'POST',
data: JSON.stringify({
media: [media],
"append": false
}),
contentType: 'application/json',
success: () => alert('Media added!\nIf you don\'t see it, try switching playlists or refreshing.'),
error: (err) => {
if (err.responseJSON.status === 'maxItems') {
alert('This playlist is full!');
} else alert('Sorry, the media couldn\'t be added.');
}
});
}
function extractCID(cid) {
return cid.replace(/(?:https?:)?(?:\/\/)?(?:[0-9A-Z-]+\.)?(?:youtu\.be\/|youtube(?:-nocookie)?\.com\/\S*?[^\w\s-])((?!videoseries)[\w-]{11})(?=[^\w-]|$)(?![?=&+%\w.-]*(?:['"][^<>]*>|<\/a>))[?=&+%\w.-]*/gi, '$1');
}
function askCID() {
media.cid = prompt(`Importing in ${pl.name}:\nVideo's link:`);
if (!!media.cid === false) return;
media.cid = extractCID(media.cid);
completeMedia(media);
}
var $grabBtn = $('<div id="playlist-add-button" class="button" title="YouTube Grab+">'+
'<i class="fa fa-plus-square"></i>'+
'</div>');
var playlists = _.find(require.s.contexts._.defined, (m)=>m&&m.activeMedia);
function grabBtnClick() {
playlists.models.forEach((model) => {
model = model.toJSON();
if (model.visible) {
if (model.count === 200) {
alert(`${model.name} is full!`);
} else {
pl = model;
askCID();
}
}
});
}
$grabBtn.click(grabBtnClick);
$('.playlist-buttons-container .playlist-edit-group').prepend($grabBtn);
function chatCmd(cmd) {
cmd = cmd.split(' ');
if (cmd[0] === '/key' && cmd.length == 2) {
if (/AIza[0-9A-z\-_]{35}/.test(cmd[1]) === false) {
API.chatLog('This is not a valid YT API Key!');
return;
}
window.gapi.client.setApiKey(cmd[1]);
localStorage.setItem('yt-api-key', cmd[1]);
API.chatLog('Custom API key saved! You\'re a pro 👍');
} else if (cmd[0] === '/grab-btn') {
if ($('#playlist-add-button').length > 0) {
$('#playlist-add-button').remove()
}
$grabBtn.off('click');
$grabBtn.click(grabBtnClick);
$('.playlist-buttons-container .playlist-edit-group').prepend($grabBtn);
}
}
API.on(API.CHAT_COMMAND, chatCmd);
API.chatLog('YT-workaround fully loaded! Refresh to de-activate it.');
}
autoReload();
})();
@certuna
Copy link

certuna commented Mar 30, 2020

Where does this button show up? Because (see screenshot) I don't see any new button. (w/ Firefox/Greasemonkey/macOS)
image

@naganowl
Copy link

@certuna it should show up to the right of Create. I used this last week and it showed up without issue.

@certuna
Copy link

certuna commented Mar 31, 2020

Just tested in latest Chrome with WiBla's Plug-It Extension, also doesn't work. Perhaps it's a macOS thing?
image
image

@naganowl
Copy link

I'm on Mac and it worked for me. That's weird.

Another alternative is creating your own API key and setting that globally with window.gapi.client.setApiKey('API_KEY'); in the console.

@WiBla
Copy link
Author

WiBla commented Mar 31, 2020

Hello @certuna,

First of all, thanks for using my script 😃

I have had other macOs users report me the same issue. I unfortunately don't have a mac on hand on which I could try to replicate it.
I can only confirm that using the extension doesn't seem to work for some odd reason..
Could you screenshot / upload the content of the devtool's console? It's accessible via F12 or cmd+shift+i
This would really help me figure out the issue, so I can hopefully fix this for other macOs "pluggers" 😄

As an experience, could you try copy / pasting the content of this gist into the large input box on the extension's option menu?
It should look something like that :
chrome_2020-03-31_22-12-29

Thanks in advance 👍

@certuna
Copy link

certuna commented Apr 6, 2020

macOS, Chrome, Plug-It Extension:

macOS, Firefox, Greasemonkey:

  • Copy/pasting the script manually into Greasemonkey, no button
  • Automatically adding the script by clicking RAW -> add to Greasemonkey, no button

Windows 10, Firefox, Greasemonkey:

  • Copy/pasting the script manually into Greasemonkey, no button
  • Automatically adding the script by clicking RAW -> add to Greasemonkey, no button

@WiBla
Copy link
Author

WiBla commented Apr 6, 2020

Thank you very much @certuna!

Although the console doesn't provide much information, you provided enough for me to confirm that this is a Firefox related issue.
I have run some tests, and it seems that on Firefox, the user-script does not detect when plug.dj is ready (loading screen hidden) even though running the same code directly into the console seems to work.
firefox_2020-04-06_17-36-35

My guess is that this is an edge-case with either GreaseMonkey or Firefox.
I will investigate this further and keep you posted of my avancements.

@certuna
Copy link

certuna commented Apr 6, 2020

I now get "YT API didn't reply, is your key still valid?" (macOS, Chrome, Plug-It extension with the script copy/pasted) - I guess the script's hardcoded API key is blocked now? (edit: only happens with some YT videos, like these: https://www.youtube.com/watch?v=RfJ8GdXIE0A and https://www.youtube.com/watch?v=uYeo_llvh1I )

@WiBla
Copy link
Author

WiBla commented Apr 6, 2020

This is indeed most likely due to the provided key meeting its daily quota.
I'll include a /key command in the next update that will allow you to provide your own.
You'll have to wait for today. Sorry about that.

@WiBla
Copy link
Author

WiBla commented Apr 6, 2020

@certuna I've gone ahead and updated the script.
It now includes the /key command talked about earlier.
It also features better wording and error checking, so people should know for sure when their API key exceeded its quota.
I also fixed the issue when a video didn't had any -. The videos you linked should now be importable.

As for why it might not work on macOs / Firefox :
I don't know exactly why yet, but GreaseMonkey doesn't seem to work.
I strongly recommend you use TamperMonkey as it felt more polished anyway.

@JKB2K18
Copy link

JKB2K18 commented Apr 29, 2020

Where should I insert my api key in the script?

@WiBla
Copy link
Author

WiBla commented Apr 29, 2020

@JKB2K18 Have you tried to type /key [YOUR-KEY-HERE] in the chat?
(Replace [YOUR-KEY-HERE] with your API key, and don't include the square brackets)

@mmuyskens
Copy link

haven't read over the code - but why not scrape the page, instead of using an API key?

@subl-1me
Copy link

where can i generate API keys?

@WiBla
Copy link
Author

WiBla commented Jun 4, 2020

haven't read over the code - but why not scrape the page, instead of using an API key?

@mmuyskens It over-complicates things for no good reasons, + CORS would probably make it impossible (cross-domain requests have to be allowed by the domain owner, one good reason to use APIs in the first place)

where can i generate API keys?

@seneca404 I invite you to check this video on the subject: https://youtu.be/_hivX4e0S68

@csj10430
Copy link

i installed that code and got '+' button. and also api key too. but it says there is something wrong with youtube and i should check my console.

@dyzziii
Copy link

dyzziii commented Sep 25, 2020

I have the same issue as csj10430. Installed to Tampermonkey, added my own api key, can see the + button, but it says there is something wrong with youtube and i should check my console.

@danilbraun
Copy link

YouTube error: Requests from referer https://www.googleapis.com/ are blocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment