Created
December 14, 2022 10:55
-
-
Save ve3/7da585258d95bfa45779a7adef365fd1 to your computer and use it in GitHub Desktop.
JavaScript YouTube URL class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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 & 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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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