const axios = require("axios");
const cheerio = require("cheerio");
const htmlparser2 = require("htmlparser2");
const regex = /<script>\s*window\["ytInitialData"\]\s*=\s*({.*})/gm;
const playlistUrl =
"https://www.youtube.com/playlist?list=PL4fGSI1pDJn6puJdseH2Rt9sMvt9E2M4i";
const options = {
url: 'https://cors-grayhat.herokuapp.com/'+playlistUrl,
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36",
dnt: 1,
},
ctoken: null,
itct: null
};
class SearchPlaylist {
constructor(playlist = playlistUrl) {
this._options = options;
this._options.url = playlist;
this.search = this.search.bind(this);
this._format = this._format.bind(this);
this._getData = this._getData.bind(this);
this._parsePlaylistItems = this._parsePlaylistItems.bind(this);
}
_format(str) {
let m;
while ((m = regex.exec(str)) !== null) {
if (m.index === regex.lastIndex) {
regex.lastIndex++;
}
let out = m.pop();
return out;
}
return null;
}
_getData(str) {
var stack = [];
var valids = [
["[]", "{}"],
["{", "}", "[", "]"],
];
var out = "";
var symbols = "";
var inInvCommas = [-1, ""];
//console.log(str.substring(0, 20));
for (var i = 0; i < str.length; i++) {
if (stack.length == 0 && i > 0) {
//return [out,symbols];
return out;
}
var curChar = str.charAt(i);
if (inInvCommas[0] > 0) {
if (curChar == inInvCommas[1] && str.charAt(i - 1) != "\\") {
inInvCommas = [-1, ""];
}
out += curChar;
continue;
}
if (valids[1].includes(curChar)) {
stack.push(curChar);
}
if (i > 0) {
if (curChar == '"' && str.charAt(i - 1) != "\\") {
inInvCommas = [i, '"'];
}
}
if (stack.length > 1) {
if (
valids[0].includes(
`${stack[stack.length - 2]}${stack[stack.length - 1]}`
)
) {
//console.log(`${stack[stack.length-2]}${stack[stack.length-1]}`);
symbols += stack.pop();
symbols += stack.pop();
}
}
out += curChar;
}
//console.log("weird");
//console.log(stack);
//return [out,symbols];
return out;
}
_parsePlaylistItems(jsonData){
var contents = jsonData['contents']['twoColumnBrowseResultsRenderer']['tabs'][0]['tabRenderer']['content']['sectionListRenderer']['contents'][0]['itemSectionRenderer']['contents'][0]['playlistVideoListRenderer']['contents'];
var items = [];
for(var i=0;i<contents.length;i++){
var rawItem = contents[i];
let playlistVideoRenderer;
try{
playlistVideoRenderer = rawItem['playlistVideoRenderer'];
}catch(ex){
continue;
}
var thumb = `https://i.ytimg.com/vi/${playlistVideoRenderer['videoId']}/hqdefault.jpg`
var item = {
type:'video',
title:playlistVideoRenderer['title']['simpleText'],
link:'https://www.youtube.com/watch?v='+playlistVideoRenderer['videoId'],
thumbnail:thumb,
author:{
name:playlistVideoRenderer['shortBylineText']['runs'][0]['text'],
ref:'https://www.youtube.com'+playlistVideoRenderer['shortBylineText']['runs'][0]['navigationEndpoint']['commandMetadata']['webCommandMetadata']['url'],
verified:false
},
description:'',
views:0,
duration:playlistVideoRenderer['lengthText']['simpleText'],
uploaded_at:'',
}
items.push(item);
}
return items;
}
async searchPlaylist() {
var res = await axios.default.get(this._options.url,{headers: options.headers});
var data = htmlparser2.parseDOM(res.data, { normalizeWhitespace: true });
var data2 = cheerio.load(data);
var out = data2.html();
out = out.replace(/\\\\/g, "\\");
var formatted = this._format(out);
if(formatted==null) throw new Error("failed code : 92");
var parsed = this._getData(formatted);
var jsonData = JSON.parse(parsed);
return jsonData;
}
}
/**
* @classdesc Class representing a Search instance.
* @author GrayHat <grayhathacks10@gmail.com>
*/
class SearchTerm {
constructor(term='nf songs',ref=options){
/**
* @member {Object}
* @description Data to be passed
* @private
*/
this._options = ref;
/**
* @member {String}
* @description Search url
* @private
*/
this._url = 'https://www.youtube.com/results?pbj=1&search_query='+encodeURI(term);
//console.log('search',this._options.ctoken);
this.addPrev = this.addPrev.bind(this);
this.search = this.search.bind(this);
/**
* @member {Object}
* @description Headers to be used
* @private
*/
this._headers = {
'Accept-Encoding': 'gzip',
'x-youtube-client-name': '1',
'x-youtube-client-version': '2.20200508.00.01'
}
/**
* @member {Object}
* @description Recieved Data
* @private
*/
this._data = {};
}
/**
* @description used to add reference for continuing search
*/
addPrev(){
if(this._options.ctoken!=null){
this._url += '&ctoken='+this._options.ctoken;
this._url += '&continuation='+this._options.ctoken;
}
if(this._options.itct!=null){
this._url += '&itct='+this._options.itct;
}
}
/**
* @description this method searches for YT videos
* @async
* @public
* @access public
* @returns {Promise<{items:Array<Object>,nextpageRef:Object}>}
*/
async search(){
var res = await axios.default.get(this._url,{headers:this._headers});
this._data = res.data;
var items = [];
var dat=this._data[1]['response']['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'];
dat = dat[0]['itemSectionRenderer']['contents'];
for(var i=0;i<dat.length;i++){
var itm = dat[i]['videoRenderer'];
if(!itm) continue;
var itemOutline = {
type:'video',
live:false,
title:'',
link:'',
thumbnail:'',
author:{
name:'',
ref:'',
verified:false
},
description:'',
views:0,
duration:'00:00',
uploaded_at:'',
};
var newItem = itemOutline;
newItem.type = 'video';
try{
newItem.title = itm['title']['runs'][0]['text'];
}catch(err){};
try{
newItem.link = `https://www.youtube.com/watch?v=${itm['videoId']}`
}catch(err){};
try{
newItem.thumbnail = itm['thumbnail']['thumbnails'][0]['url'].substring(0,49);
}catch(err){};
try{
newItem.author.name = itm['ownerText']['runs'][0]['text'];
}catch(err){};
try{
newItem.author.ref = 'https://www.youtube.com'+itm['ownerText']['runs'][0]['navigationEndpoint']['commandMetadata']['webCommandMetadata']['url'];
}catch(err){};
try{
newItem.author.verified = itm['ownerBadges']['metadataBadgeRenderer']['icon']['iconType']=='OFFICIAL_ARTIST_BADGE' ? true:false;
}catch(err){
newItem.author.verified = false;
//console.log(err);
}
var desc = '';
try{
for(var j=0;j<itm['descriptionSnippet']['runs'].length;j++){
try{
var txt = itm['descriptionSnippet']['runs'][j]['text'];
desc+=txt;
}catch(err){}
}
}catch(err){};
newItem.description = desc;
try{
newItem.views = itm['viewCountText']['simpleText'];
}catch(err){};
try{
newItem.duration = itm['lengthText']['simpleText'];
}catch(err){};
try{
newItem.uploaded_at = itm['publishedTimeText']['simpleText'];
}catch(err){};
items.push(newItem);
//console.log('search190',newItem.title);
}
var nref = this._data[1]['response']['contents']['twoColumnSearchResultsRenderer']['primaryContents']['sectionListRenderer']['contents'][0]['itemSectionRenderer']['continuations'][0]['nextContinuationData'];
this._options.ctoken = nref['continuation'];
this._options.itct = nref['clickTrackingParams'];
return {
items: items,
nextpageRef: this._options,
};
}
/**
* @description this method continues search for YT videos
* @async
* @public
* @access public
* @returns {Promise<{items:Array<Object>,nextpageRef:Object}>}
*/
async ContSearch(){
var res = await axios.default.get(this._url,{headers:this._headers});
this._data = res.data;
var items = [];
var dat=this._data[1]['response']['continuationContents']['itemSectionContinuation']['contents'];
for(var i=0;i<dat.length;i++){
try{
var itm = dat[i]['videoRenderer'];
}catch(err){continue;};
if(!itm) continue;
var itemOutline = {
type:'video',
live:false,
title:'',
link:'',
thumbnail:'',
author:{
name:'',
ref:'',
verified:false
},
description:'',
views:0,
duration:'00:00',
uploaded_at:'',
};
var newItem = itemOutline;
newItem.type = 'video';
try{
newItem.title = itm['title']['runs'][0]['text'];
}catch(err){};
try{
newItem.link = `https://www.youtube.com/watch?v=${itm['videoId']}`;
}catch(err){};
try{
newItem.thumbnail = itm['thumbnail']['thumbnails'][0]['url'].substring(0,49);
}catch(err){};
try{
newItem.author.name = itm['ownerText']['runs'][0]['text'];
}catch(err){};
try{
newItem.author.ref = 'https://www.youtube.com'+itm['ownerText']['runs'][0]['navigationEndpoint']['commandMetadata']['webCommandMetadata']['url'];
}catch(err){};
try{
newItem.author.verified = itm['ownerBadges']['metadataBadgeRenderer']['icon']['iconType']=='OFFICIAL_ARTIST_BADGE' ? true:false;
}catch(err){
newItem.author.verified = false;
//console.log(err);
}
var desc = '';
try{
for(var j=0;j<itm['descriptionSnippet']['runs'].length;j++){
try{
var txt = itm['descriptionSnippet']['runs'][j]['text'];
desc+=txt;
}catch(err){};
}
}catch(err){};
try{
newItem.description = desc;
}catch(err){};
try{
newItem.views = itm['viewCountText']['simpleText'];
}catch(err){};
try{
newItem.duration = itm['lengthText']['simpleText'];
}catch(err){};
try{
newItem.uploaded_at = itm['publishedTimeText']['simpleText'];
}catch(err){};
//console.log(newItem.title);
items.push(newItem);
//console.log('search190',newItem.title);
}
var nref = this._data[1]['response']['continuationContents']['itemSectionContinuation']['continuations'][0]['nextContinuationData'];
this._options.ctoken = nref['continuation'];
this._options.itct = nref['clickTrackingParams'];
return {
items: items,
nextpageRef: this._options,
};
}
/**
* @public
* @member
* @description returns the url being used to search
*/
get url(){
return this._url;
}
}
module.exports = SearchTerm;