Skip to content

Instantly share code, notes, and snippets.

@ve3
Created December 14, 2022 10:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ve3/7da585258d95bfa45779a7adef365fd1 to your computer and use it in GitHub Desktop.
Save ve3/7da585258d95bfa45779a7adef365fd1 to your computer and use it in GitHub Desktop.
JavaScript YouTube URL class
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>YouTube URL</title>
<style>
.txterror, .txt-errror, .txt_error {
color: red;
font-size: x-large;
}
.txtsuccess, .txt-success, .txt_success {
color: green;
}
.txtwarn, .txt-warn, .txt_warn {
color: orange;
font-size: x-large;
}
</style>
</head>
<body>
<div id="debug"></div>
<script type="application/javascript" src="assets/js/YouTubeURL.js"></script>
<script type="application/javascript">
const debugE = document.getElementById('debug');
const YouTubeURLObj = new YouTubeURL();
const youtubeUrls = [
// not YouTube URL.
{
'url': 'https://google.com',
'isYouTubeURL': false,
},
{
'url': 'https://youtu.be',
'isYouTubeURL': true,
},
{
'url': 'https://youtube.com',
'isYouTubeURL': true,
},
{
'url': 'https://www.youtube.com/@BrooklynDuo/about',
'isYouTubeURL': true,
},
// tests on short domain --------------------
{
'url': 'https://youtu.be/Ptk_1Dc2iPY?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': 'https://youtu.be/Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://m.youtu.be/Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://youtu.be/Ptk_1Dc2iPY?feature=channel',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
// tests on youtube.com domain --------------
{
'url': 'https://www.youtube.com/embed/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://m.youtube.com/embed/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://youtube.com/embed/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://www.youtube.com/embed/Ptk_1Dc2iPY?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /playlist path ---------------------------
{
'url': 'https://www.youtube.com/playlist?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 2,
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': 'https://m.youtube.com/playlist?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 2,
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /v path ----------------------------------
{
'url': 'https://www.youtube.com/v/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://m.youtube.com/v/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://www.youtube.com/v/Ptk_1Dc2iPY?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': 'https://m.youtube.com/v/Ptk_1Dc2iPY?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /watch path ------------------------------
{
'url': 'https://www.youtube.com/watch?v=Ptk_1Dc2iPY&list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': 'https://www.youtube.com/watch?v=Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://m.youtube.com/watch?v=Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://youtube.com/watch?v=Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
// /shorts path -----------------------------
{
'url': 'https://www.youtube.com/shorts/TkQew-mbjqY',
'isYouTubeURL': 1,
'videoId': 'TkQew-mbjqY',
},
{
'url': 'https://m.youtube.com/shorts/TkQew-mbjqY',
'isYouTubeURL': 1,
'videoId': 'TkQew-mbjqY',
},
{
'url': 'https://youtube.com/shorts/TkQew-mbjqY?feature=share',
'isYouTubeURL': 1,
'videoId': 'TkQew-mbjqY',
},
// no protocol (http), just start with double slash.
{
'url': '//www.youtube.com/watch?v=Ptk_1Dc2iPY&list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': '//www.youtube.com/watch?v=Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': '//m.youtube.com/watch?v=Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
// tests on youtube-nocookie.com domain ------------
// /embed path ------------------------------
{
'url': 'https://www.youtube-nocookie.com/embed/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://m.youtube-nocookie.com/embed/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://www.youtube-nocookie.com/embed/Ptk_1Dc2iPY?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /playlist path ---------------------------
{
'url': 'https://www.youtube-nocookie.com/playlist?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 2,
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': 'https://m.youtube-nocookie.com/playlist?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 2,
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /v path ----------------------------------
{
'url': 'https://www.youtube-nocookie.com/v/Ptk_1Dc2iPY?rel=0',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
{
'url': 'https://www.youtube-nocookie.com/v/Ptk_1Dc2iPY?list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /watch path ------------------------------
{
'url': 'https://www.youtube-nocookie.com/watch?v=Ptk_1Dc2iPY&list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
// /shorts path -----------------------------
{
'url': 'https://www.youtube-nocookie.com/shorts/TkQew-mbjqY',
'isYouTubeURL': 1,
'videoId': 'TkQew-mbjqY',
},
// no protocol (http), just start with double slash.
{
'url': '//www.youtube-nocookie.com/watch?v=Ptk_1Dc2iPY&list=PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
'isYouTubeURL': 3,
'videoId': 'Ptk_1Dc2iPY',
'playlistId': 'PLXdaORVZTx4AZlYj-9LWJomnzGE6lthbJ',
},
{
'url': '//m.youtube-nocookie.com/watch?v=Ptk_1Dc2iPY',
'isYouTubeURL': 1,
'videoId': 'Ptk_1Dc2iPY',
},
];
for (const eachSet of youtubeUrls) {
YouTubeURLObj.parseUrl(eachSet.url);
const YTData = YouTubeURLObj.getData();
let debugString = '<p>URL: ' + eachSet.url + '<br>';
if (typeof(eachSet.isYouTubeURL) !== 'undefined') {
const isYTUrlResult = YouTubeURLObj.isYouTubeURL();
debugString += 'Is YouTube URL: ';
if (isYTUrlResult === false) {
debugString += '<span class="txterror">No</span>';
} else {
if (isYTUrlResult === eachSet.isYouTubeURL) {
debugString += '<span class="txtsuccess">';
} else {
debugString += '<span class="txtwarn">';
}
if (isYTUrlResult === true) {
debugString += 'Yes';
} else if (isYTUrlResult === 1) {
debugString += 'Is video';
} else if (isYTUrlResult === 2) {
debugString += 'Is playlist';
} else if (isYTUrlResult === 3) {
debugString += 'Is video &amp; playlist';
} else {
debugString += '<span>' + isYTUrlResult + '';
}
debugString += '</span>';
}
debugString += '<br>';
}
if (typeof(eachSet.videoId) === 'string') {
debugString += 'Video ID: ';
if (eachSet.videoId === YTData.videoId) {
debugString += '<span class="txtsuccess">';
} else {
debugString += '<span class="txtwarn">';
}
debugString += YTData.videoId;
if (eachSet.videoId !== YTData.videoId) {
debugString += ' (expect ' + eachSet.videoId + ' : actual ' + YTData.videoId + ')';
}
debugString += '</span>';
debugString += '<br>';
}
if (typeof(eachSet.playlistId) === 'string') {
debugString += 'Playlist ID: ';
if (eachSet.playlistId === YTData.playlistId) {
debugString += '<span class="txtsuccess">';
} else {
debugString += '<span class="txtwarn">';
}
debugString += YTData.playlistId;
if (eachSet.playlistId !== YTData.playlistId) {
debugString += ' (expect ' + eachSet.playlistId + ' : actual ' + YTData.playlistId + ')';
}
debugString += '</span>';
}
debugString += '</p>';
debugE.insertAdjacentHTML('beforeend', debugString);
}
</script>
</body>
</html>
/**
* YouTube URL.
*
* @author Vee W.
*/
class YouTubeURL {
/**
* @type {string} YouTube URL.
*/
url;
/**
* @type {object} The object returned from `new URL()`.
*/
URLObj;
/**
* Class constructor.
*
* @see this.parseUrl()
* @param {string} url The URL to check or get value.
*/
constructor(url) {
if (typeof(url) === 'string') {
this.parseUrl(url);
}
}// constructor
/**
* Get YouTube Data.
*
* @returns {mixed} Return object with `'videoId'`, `'playlistId'` keys if valid but these keys maybe empty if not found, <br>
* return `false` if not YouTube.
*/
getData() {
if (typeof(this.url) !== 'string' || typeof(this.URLObj) !== 'object') {
throw new Error('Invalid URL.');
}
const youtubeDomains = [
'youtube.com',
'youtube-nocookie.com',
'youtu.be',
];
let foundDomain = false;
for (const eachDomain of youtubeDomains) {
if (this.URLObj.host.toLowerCase().indexOf(eachDomain.toLowerCase()) !== -1) {
foundDomain = true;
break;
}
}
if (false === foundDomain) {
return false;
}
if (this.URLObj.host.toLowerCase().indexOf('youtu.be') !== -1) {
// if short URL of YouTube.
const videoId = this.URLObj.pathname.replace(/^\/|\/$/g, '');
const playlistId = this.URLObj.searchParams.get('list');
const output = {
'videoId': (typeof(videoId) === 'string' ? videoId : ''),
'playlistId': (typeof(playlistId) === 'string' ? playlistId : ''),
};
return output;
}
// come to this means the domain is one of full domain. ----------------------
const pathName = this.URLObj.pathname.replace(/^\/|\/$/g, '');// trim slashes.
const splittedPath = pathName.split('/');
let videoId;
if (
typeof(splittedPath[0]) === 'string' &&
(
splittedPath[0].toLowerCase() === 'embed' ||
splittedPath[0].toLowerCase() === 'v' ||
splittedPath[0].toLowerCase() === 'shorts'
)
) {
// if matched domain.tld/embed
// OR domain.tld/v
// OR domain.tld/shorts
// the 2nd value ([1]) is video ID.
videoId = splittedPath[1];
} else {
videoId = this.URLObj.searchParams.get('v');
}
let playlistId = this.URLObj.searchParams.get('list');
const output = {
'videoId': (typeof(videoId) === 'string' ? videoId : ''),
'playlistId': (typeof(playlistId) === 'string' ? playlistId : ''),
};
return output;
}// getData
/**
* Check if this is YouTube URL.
*
* @returns {mixed} Return multiply types. Return `false` if not YouTube URL at all, <br>
* return `1` (int) if contains video ID, <br>
* return `2` (int) if contains playlist ID, <br>
* return `3` (int) if contains both video ID and play list ID,<br>
* return `true` if it is other YouTube URLs.
*/
isYouTubeURL() {
if (typeof(this.url) !== 'string' || typeof(this.URLObj) !== 'object') {
console.error('Invalid URL.');
return false;
}
const YTData = this.getData();
if (false === YTData) {
return false;
}
if (YTData.videoId !== '' && YTData.playlistId !== '') {
return 3;
} else if (YTData.videoId !== '') {
return 1;
} else if (YTData.playlistId !== '') {
return 2;
}
// to here means it is YouTube URL but go to somewhere else that is not video, shorts, playlist.
return true;
}// isYouTubeURL
/**
* Parse URL.
*
* @param {string} url The URL to check or get value.
*/
parseUrl(url) {
if (typeof(url) !== 'string') {
throw new Error('The URL is not string.');
}
if (url.match(/^\/\//)) {
// if found double slash at beginning. example //youtube.com/x/y
// prepend https to prevent error.
url = 'https:' + url;
}
this.url = url;
this.URLObj = new URL(this.url);
if (
!this.URLObj.host ||
this.URLObj.host === '' ||
typeof(this.URLObj.pathname) !== 'string'
) {
throw new Error('Invalid URL.');
}
}// parseUrl
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment