diff --git a/.gitignore b/.gitignore index 033de71..fe525ee 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ dist package-lock.json temp.js temp.json +.vscode/settings.json diff --git a/NUXT/pages/home.vue b/NUXT/pages/home.vue index fe17ca5..e06e3aa 100644 --- a/NUXT/pages/home.vue +++ b/NUXT/pages/home.vue @@ -1,24 +1,68 @@ // Buttons and methods for testing and demonstration purposes only. Uncomment them to see how it works. Remove to actually implement a implementation \ No newline at end of file diff --git a/NUXT/pages/index.vue b/NUXT/pages/index.vue index 549fb07..e5bbbb3 100644 --- a/NUXT/pages/index.vue +++ b/NUXT/pages/index.vue @@ -11,8 +11,9 @@ diff --git a/NUXT/plugins/innertube.js b/NUXT/plugins/innertube.js index 05b1139..789565a 100644 --- a/NUXT/plugins/innertube.js +++ b/NUXT/plugins/innertube.js @@ -18,37 +18,37 @@ class Innertube { return typeof this.ErrorCallback === "function" } - init() { - Http.get({ url: constants.URLS.YT_URL, params: { hl: "en" } }) - .then(result => { - if (result instanceof Error && this.checkErrorCallback) this.ErrorCallback(result.message, true); - try { - const data = JSON.parse(getBetweenStrings(result.data, 'ytcfg.set(', ');')); - if (data.INNERTUBE_CONTEXT) { - this.key = data.INNERTUBE_API_KEY; - this.context = data.INNERTUBE_CONTEXT; - this.context.client.clientName = "ANDROID"; - this.context.client.clientVersion = "16.25"; - } - - } catch (err) { - console.log(err) - if (this.checkErrorCallback) this.ErrorCallback(err, true) - if (this.retry_count >= 10) { this.init() } else { if (this.checkErrorCallback) this.ErrorCallback("Failed to retrieve Innertube session", true); } + async initAsync() { + const html = await Http.get({ url: constants.URLS.YT_URL, params: { hl: "en" } }).catch((error) => error); + try { + if (html instanceof Error && this.checkErrorCallback) this.ErrorCallback(html.message, true); + try { + const data = JSON.parse(getBetweenStrings(html.data, 'ytcfg.set(', ');')); + if (data.INNERTUBE_CONTEXT) { + this.key = data.INNERTUBE_API_KEY; + this.context = data.INNERTUBE_CONTEXT; + this.context.client = constants.INNERTUBE_CLIENT(this.context.client) } - }) - .catch((error) => error); + + } catch (err) { + console.log(err) + if (this.checkErrorCallback) this.ErrorCallback(err, true) + if (this.retry_count >= 10) { this.initAsync() } else { if (this.checkErrorCallback) this.ErrorCallback("Failed to retrieve Innertube session", true); } + } + } catch (error) { + this.ErrorCallback(error, true) + }; }; - static create(ErrorCallback) { + static async createAsync(ErrorCallback) { const created = new Innertube(ErrorCallback); - created.init(); + await created.initAsync(); return created; } //--- API Calls ---// - async browse(action_type) { + async browseAsync(action_type) { let data = { context: this.context } switch (action_type) { @@ -69,7 +69,7 @@ class Innertube { headers: { "Content-Type": "application/json" } }).catch((error) => error); - if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; + if (response instanceof Error) return { success: false, status_code: response.status, message: response.message }; return { success: true, @@ -78,13 +78,24 @@ class Innertube { }; } - async getVidInfo(id) { + static getThumbnail(id, resolution) { + switch (resolution) { + case "min": + return `https://img.youtube.com/vi/${id}/mqdefault.jpg` + case "mid": + return `https://img.youtube.com/vi/${id}/hqdefault.jpg` + default: + return `https://img.youtube.com/vi/${id}/maxresdefault.jpg` + } + } + + async getVidInfoAsync(id) { let data = { context: this.context, videoId: id } const response = await Http.post({ url: `${constants.URLS.YT_BASE_API}/player?key=${this.key}`, data: data, - headers: { "Content-Type": "application/json" } + headers: constants.INNERTUBE_HEADER(this.context) }).catch((error) => error); if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message }; @@ -97,8 +108,10 @@ class Innertube { } // Simple Wrappers - async getRecommendations() { - return await this.browse("recommendations") + async getRecommendationsAsync() { + const rec = await this.browseAsync("recommendations"); + console.log(rec.data) + return rec.data; } diff --git a/NUXT/plugins/youtube.js b/NUXT/plugins/youtube.js index 3f94c12..1568fe1 100644 --- a/NUXT/plugins/youtube.js +++ b/NUXT/plugins/youtube.js @@ -128,23 +128,33 @@ const searchModule = { //--- Recommendations --// -// Immediately create an Innertube object. This will be the object used in all future Inntertube API calls +let InnertubeAPI; + +// Lazy 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 = { - recommendAPI: Innertube.create((message, isError) => { logger("Innertube", message, isError); }), // There's definitely a better way to do this, but it's 2 am and I just can't anymore + + async getAPI() { + if (!InnertubeAPI) { + InnertubeAPI = await Innertube.createAsync((message, isError) => { logger("Innertube", message, isError); }) + } + return InnertubeAPI; + }, async getVid(id) { - console.log(this.recommendAPI) - return this.recommendAPI.getVidInfo(id); + return InnertubeAPI.getVidInfoAsync(id).data; }, async recommend() { - return this.recommendAPI.getRecommendations(); + return InnertubeAPI.getRecommendationsAsync(); }, + + getThumbnail: (id, resolution) => Innertube.getThumbnail(id, resolution) } //--- Start ---// export default ({ app }, inject) => { - inject('youtube', {...searchModule, ...recommendationModule }) + inject('youtube', {...searchModule, ...recommendationModule, }) + inject("logger", logger) } logger("Initialize", "Program Started"); \ No newline at end of file diff --git a/NUXT/static/constants.js b/NUXT/static/constants.js index 8ccfee3..3e4d1c9 100644 --- a/NUXT/static/constants.js +++ b/NUXT/static/constants.js @@ -1,11 +1,53 @@ // To centeralize certain values and URLs as for easier debugging and refactoring +const url = { + YT_URL: 'https://www.youtube.com', + YT_MUSIC_URL: 'https://music.youtube.com', + YT_BASE_API: 'https://www.youtube.com/youtubei/v1', + YT_SUGGESTIONS: "https://suggestqueries.google.com/complete", + VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube", +} + +const ytApiVal = { + VERSION: "16.25", + CLIENTNAME: "ANDROID", +} + module.exports = { - URLS: { - YT_URL: 'https://www.youtube.com', - YT_MUSIC_URL: 'https://music.youtube.com', - YT_BASE_API: 'https://www.youtube.com/youtubei/v1', - YT_SUGGESTIONS: "https://suggestqueries.google.com/complete", - VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube", + URLS: url, + YT_API_VALUES: ytApiVal, + + INNERTUBE_HEADER: (info) => { + let headers = { + 'accept': '*/*', + 'user-agent': info.client.userAgent, + 'content-type': 'application/json', + 'x-goog-authuser': 0, + 'x-youtube-client-name': 2, + 'x-youtube-client-version': info.client.clientVersion, + 'x-youtube-chrome-connected': 'source=Chrome,mode=0,enable_account_consistency=true,supervised=false,consistency_enabled_by_default=false', + }; + return headers + }, + + INNERTUBE_CLIENT: (info) => { + let client = { + "gl": info.gl, + "hl": info.hl, + "deviceMake": info.deviceMake, + "deviceModel": info.deviceModel, + "userAgent": info.userAgent, + "clientName": ytApiVal.CLIENTNAME, + "clientVersion": ytApiVal.VERSION, + "osName": info.osName, + "osVersion": info.osVersion, + "platform": "MOBILE", + "originalUrl": info.originalUrl, + "configInfo": info.configInfo, + "remoteHost": info.remoteHost, + "visitorData": info.visitorData, + // This is, by all accounts, a horrible implementation, but this is currently the only solution besides + }; + return client } } \ No newline at end of file