mirror of
https://github.com/VueTubeApp/VueTube
synced 2024-11-23 03:35:15 +00:00
251 lines
9.7 KiB
JavaScript
251 lines
9.7 KiB
JavaScript
//--- Modules/Imports ---//
|
|
import { Http } from '@capacitor-community/http';
|
|
import Innertube from './innertube'
|
|
import constants from '../static/constants';
|
|
|
|
//--- Logger Function ---//
|
|
function logger(func, data, isError = false) {
|
|
searchModule.logs.unshift({
|
|
name: func,
|
|
time: Date.now(),
|
|
data: data,
|
|
error: isError
|
|
})
|
|
}
|
|
|
|
//--- Youtube Base Parser ---//
|
|
function youtubeParse(html, callback) {
|
|
//--- Replace Encoded Characters ---///
|
|
html = html.replace(/\\x([0-9A-F]{2})/ig, (...items) => { return String.fromCharCode(parseInt(items[1], 16)); });
|
|
//--- Properly Format JSON ---//
|
|
html = html.replaceAll("\\\\\"", "");
|
|
//--- Parse JSON ---//
|
|
html = JSON.parse(html);
|
|
|
|
//--- Get Results ---// ( Thanks To appit-online On Github ) -> https://github.com/appit-online/youtube-search/blob/master/src/lib/search.ts
|
|
let results;
|
|
if (html && html.contents && html.contents.sectionListRenderer && html.contents.sectionListRenderer.contents &&
|
|
html.contents.sectionListRenderer.contents.length > 0 &&
|
|
html.contents.sectionListRenderer.contents[0].itemSectionRenderer &&
|
|
html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents.length > 0) {
|
|
results = html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents;
|
|
logger(constants.LOGGER_NAMES.search, results);
|
|
callback(results);
|
|
} else {
|
|
try {
|
|
results = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]);
|
|
logger(constants.LOGGER_NAMES.search, results);
|
|
callback(results);
|
|
} catch (e) {}
|
|
try {
|
|
results = JSON.parse(html.split('{"itemSectionRenderer":')[html.split('{"itemSectionRenderer":').length - 1].split('},{"continuationItemRenderer":{')[0]).contents;
|
|
logger(constants.LOGGER_NAMES.search, results);
|
|
callback(results);
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
//--- Search Main Function ---//
|
|
function youtubeSearch(text, callback) {
|
|
Http.request({
|
|
method: 'GET',
|
|
url: `${constants.URLS.YT_URL}/results`,
|
|
params: { q: text, hl: "en" }
|
|
})
|
|
.then((res) => {
|
|
//--- Get HTML Only ---//
|
|
let html = res.data;
|
|
//--- Isolate The Script Containing Video Information ---//
|
|
html = html.split("var ytInitialData = '")[1].split("';</script>")[0];
|
|
|
|
youtubeParse(html, (data) => {
|
|
callback(data);
|
|
})
|
|
|
|
|
|
|
|
})
|
|
.catch((err) => {
|
|
logger(constants.LOGGER_NAMES.search, err, true);
|
|
callback(err);
|
|
});
|
|
}
|
|
|
|
const searchModule = {
|
|
logs: new Array(),
|
|
|
|
//--- Get YouTube's Search Auto Complete ---//
|
|
autoComplete(text, callback) {
|
|
Http.request({
|
|
method: 'GET',
|
|
url: `${constants.URLS.YT_SUGGESTIONS}/search`,
|
|
params: { client: 'youtube', q: text }
|
|
})
|
|
.then((res) => {
|
|
logger(constants.LOGGER_NAMES.autoComplete, res);
|
|
callback(res.data);
|
|
})
|
|
.catch((err) => {
|
|
logger(constants.LOGGER_NAMES.autoComplete, err, true);
|
|
callback(err);
|
|
});
|
|
},
|
|
|
|
search(text, callback) {
|
|
|
|
let results = new Array();
|
|
youtubeSearch(text, (videos) => {
|
|
for (const i in videos) {
|
|
const video = videos[i];
|
|
|
|
if (video.compactVideoRenderer) {
|
|
//--- If Entry Is A Video ---//
|
|
results.push({
|
|
id: video.compactVideoRenderer.videoId,
|
|
title: video.compactVideoRenderer.title.runs[0].text,
|
|
runtime: video.compactVideoRenderer.lengthText.runs[0].text,
|
|
uploaded: video.compactVideoRenderer.publishedTimeText.runs[0].text,
|
|
views: video.compactVideoRenderer.viewCountText.runs[0].text,
|
|
thumbnails: video.compactVideoRenderer.thumbnail.thumbnails
|
|
})
|
|
} else {
|
|
//--- If Entry Is Not A Video ---//
|
|
//logger(constants.LOGGER_NAMES.search, { type: "Error Caught Successfully", error: video }, true);
|
|
}
|
|
|
|
|
|
}
|
|
})
|
|
callback(results);
|
|
|
|
},
|
|
|
|
getRemainingVideoInfo(id, callback) {
|
|
String.prototype.decodeEscapeSequence = function() {
|
|
return this.replace(/\\x([0-9A-Fa-f]{2})/g, function() {
|
|
return String.fromCharCode(parseInt(arguments[1], 16));
|
|
});
|
|
};
|
|
Http.request({
|
|
method: 'GET',
|
|
url: `${constants.URLS.YT_URL}/watch`,
|
|
params: { v: id }
|
|
})
|
|
.then((res) => {
|
|
let dataUpdated = res.data.decodeEscapeSequence()
|
|
let likes = dataUpdated.split(`"defaultIcon":{"iconType":"LIKE"},"defaultText":{"runs":[{"text":"`)[1].split(`"}],"accessibility":`)[0]
|
|
let uploadDate = dataUpdated.split(`"uploadDate":"`)[1].split(`}},"trackingParams":"`)[0].slice(0, -2);
|
|
let data = {
|
|
"likes": likes,
|
|
"uploadDate": uploadDate
|
|
}
|
|
logger("vidData", data)
|
|
callback(data)
|
|
})
|
|
.catch((err) => {
|
|
logger("codeRun", err, true);
|
|
callback(err);
|
|
});
|
|
|
|
},
|
|
|
|
getReturnYoutubeDislike(id, callback) {
|
|
Http.request({
|
|
method: 'GET',
|
|
url: `https://returnyoutubedislikeapi.com/votes`,
|
|
params: { videoId: id }
|
|
})
|
|
.then((res) => {
|
|
logger("rydData", res.data)
|
|
callback(res.data)
|
|
})
|
|
.catch((err) => {
|
|
logger("codeRun", err, true);
|
|
callback(err);
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
//--- Recommendations --//
|
|
|
|
let InnertubeAPI;
|
|
|
|
// Loads Innertube object. This will be the object used in all future Innertube API calls. Code provided by Lightfire228 (https://github.com/Lightfire228)
|
|
// These are just a way for the backend Javascript to communicate with the front end Vue scripts. Essentially a wrapper inside a wrapper
|
|
const recommendationModule = {
|
|
|
|
async getAPI() {
|
|
if (!InnertubeAPI) {
|
|
InnertubeAPI = await Innertube.createAsync((message, isError) => { logger(constants.LOGGER_NAMES.innertube, message, isError); })
|
|
}
|
|
return InnertubeAPI;
|
|
},
|
|
|
|
async getVid(id) {
|
|
|
|
// temporary test
|
|
|
|
const html = await Http.get({
|
|
url: "https://m.youtube.com/watch?v=U-9M-BjFYMc&t=8s&pbj=1",
|
|
params: {},
|
|
headers: {
|
|
accept: '*/*',
|
|
'user-agent': 'Mozilla/5.0 (Linux; Android 10; WP7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.101 Mobile Safari/537.36',
|
|
'content-type': 'application/json',
|
|
'accept-language': 'en-US,en;q=0.9',
|
|
'x-goog-authuser': 0,
|
|
'x-goog-visitor-id': 'CgtsaVdQdGhfbVNOMCiC0taRBg%3D%3D',
|
|
'x-youtube-client-name': 2,
|
|
'x-youtube-client-version': '2.20220318.00.00',
|
|
'x-youtube-chrome-connected': 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false',
|
|
'x-origin': 'https://m.youtube.com',
|
|
origin: 'https://m.youtube.com',
|
|
referer: 'https://m.youtube.com/watch?v=U-9M-BjFYMc'
|
|
}
|
|
}).catch((error) => error);
|
|
console.log(html.data)
|
|
return InnertubeAPI.getVidInfoAsync(id);
|
|
},
|
|
|
|
// It just works™
|
|
async recommend() {
|
|
const response = await InnertubeAPI.getRecommendationsAsync();
|
|
if (!response.success) throw new Error("An error occurred and innertube failed to respond")
|
|
|
|
const contents = response.data.contents.singleColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents
|
|
return contents.map((shelves) => {
|
|
const video = shelves.shelfRenderer?.content?.horizontalListRenderer?.items
|
|
|
|
if (video) return video.map((item) => {
|
|
item = item.gridVideoRenderer
|
|
if (item) return {
|
|
id: item.videoId,
|
|
title: item.title?.runs[0].text,
|
|
thumbnail: this.getThumbnail(item.videoId),
|
|
channel: item.shortBylineText?.runs[0] ? item.shortBylineText.runs[0].text : item.longBylineText?.runs[0].text,
|
|
channelURL: `${constants.YT_URL}/${(item.shortBylineText?.runs[0] ? item.shortBylineText.runs[0] : item.longBylineText?.runs[0]).navigationEndpoint?.browseEndpoint?.canonicalBaseUrl}`,
|
|
channelThumbnail: item.channelThumbnail?.thumbnails[0],
|
|
metadata: {
|
|
published: item.publishedTimeText?.runs[0].text,
|
|
views: item.shortViewCountText?.runs[0].text,
|
|
length: item.publishedTimeText?.runs[0].text,
|
|
overlayStyle: item.thumbnailOverlays?.map(overlay => overlay.thumbnailOverlayTimeStatusRenderer?.style),
|
|
overlay: item.thumbnailOverlays?.map(overlay => overlay.thumbnailOverlayTimeStatusRenderer?.text.runs[0].text),
|
|
},
|
|
};
|
|
else return undefined
|
|
})
|
|
|
|
})
|
|
},
|
|
|
|
getThumbnail: (id, resolution) => Innertube.getThumbnail(id, resolution)
|
|
}
|
|
|
|
//--- Start ---//
|
|
export default ({ app }, inject) => {
|
|
inject('youtube', {...searchModule, ...recommendationModule, })
|
|
inject("logger", logger)
|
|
}
|
|
logger(constants.LOGGER_NAMES.init, "Program Started");
|