mirror of
https://github.com/VueTubeApp/VueTube
synced 2024-11-26 21:23:02 +00:00
262 lines
7.8 KiB
JavaScript
262 lines
7.8 KiB
JavaScript
//--- Modules/Imports ---//
|
|
import { Http } from "@capacitor-community/http";
|
|
import Innertube from "./innertube";
|
|
import constants from "./constants";
|
|
import useRender from "./renderers";
|
|
|
|
//--- 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})/gi, (...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. getAPI 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 innertubeModule = {
|
|
async getAPI() {
|
|
if (!InnertubeAPI) {
|
|
InnertubeAPI = await Innertube.createAsync((message, isError) => {
|
|
logger(constants.LOGGER_NAMES.innertube, message, isError);
|
|
});
|
|
}
|
|
return InnertubeAPI;
|
|
},
|
|
|
|
async getVid(id) {
|
|
try {
|
|
return await InnertubeAPI.VidInfoAsync(id);
|
|
} catch (error) {
|
|
logger(constants.LOGGER_NAMES.watch, error, true);
|
|
}
|
|
},
|
|
|
|
getThumbnail(id, resolution) {
|
|
if (resolution == "max") {
|
|
const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg`;
|
|
let img = new Image();
|
|
img.src = url;
|
|
img.onload = function () {
|
|
if (img.height !== 120) return url;
|
|
};
|
|
}
|
|
return `https://img.youtube.com/vi/${id}/mqdefault.jpg`;
|
|
},
|
|
|
|
// It just works™
|
|
// Front page recommendation
|
|
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;
|
|
const final = contents.map((shelves) => {
|
|
const video =
|
|
shelves.shelfRenderer?.content?.horizontalListRenderer?.items;
|
|
|
|
if (video) return video;
|
|
});
|
|
console.log(final);
|
|
return final;
|
|
},
|
|
|
|
async search(query) {
|
|
try {
|
|
const response = await InnertubeAPI.getSearchAsync(query);
|
|
return response.content.verticalListRenderer;
|
|
} catch (err) {
|
|
logger(constants.LOGGER_NAMES.search, err, true);
|
|
}
|
|
},
|
|
};
|
|
|
|
//--- Start ---//
|
|
export default ({ app }, inject) => {
|
|
inject("youtube", { ...searchModule, ...innertubeModule });
|
|
inject("logger", logger);
|
|
};
|
|
logger(constants.LOGGER_NAMES.init, "Program Started");
|