0
0
Fork 0
mirror of https://github.com/VueTubeApp/VueTube synced 2024-11-25 12:45:17 +00:00

files linted, sorry for the spam c:

This commit is contained in:
Nikita Krupin 2022-03-21 19:47:11 -04:00
parent 777a0da738
commit ad9d916627
29 changed files with 1023 additions and 866 deletions

View file

@ -1,18 +1,22 @@
<template> <template>
<v-bottom-navigation v-model="tabSelection" shift class="bottomNav py-4 accent2"> <v-bottom-navigation
v-model="tabSelection"
shift
class="bottomNav py-4 accent2"
>
<v-btn <v-btn
v-for="(item, i) in tabs" v-for="(item, i) in tabs"
:key="i" :key="i"
v-ripple="false"
class="navButton" class="navButton"
:to="item.link" :to="item.link"
plain plain
v-ripple="false"
> >
<span v-text="item.name" /> <span v-text="item.name" />
<v-icon <v-icon
v-text="item.icon"
:color="tabSelection == i ? 'primary' : 'grey'" :color="tabSelection == i ? 'primary' : 'grey'"
:class="tabSelection == i ? 'tab primaryAlt' : ''" :class="tabSelection == i ? 'tab primaryAlt' : ''"
v-text="item.icon"
/> />
</v-btn> </v-btn>
<!-- <v-btn text class="navButton mr-2 fill-height" color="white" @click="searchBtn()" <!-- <v-btn text class="navButton mr-2 fill-height" color="white" @click="searchBtn()"

View file

@ -12,12 +12,12 @@
<div style="position: relative"> <div style="position: relative">
<v-img :src="video.thumbnail" /> <v-img :src="video.thumbnail" />
<div <div
v-text="video.metadata.overlay[0]"
class="videoRuntimeFloat" class="videoRuntimeFloat"
style="color: #fff" style="color: #fff"
v-text="video.metadata.overlay[0]"
/> />
</div> </div>
<div v-text="video.title" style="margin-top: 0.5em" /> <div style="margin-top: 0.5em" v-text="video.title" />
<div v-text="parseBottom(video)" /> <div v-text="parseBottom(video)" />
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -54,4 +54,4 @@ export default {
}, },
}, },
}; };
</script> </script>

View file

@ -4,22 +4,27 @@
color="accent2" color="accent2"
class="topNav rounded-0 pa-3" class="topNav rounded-0 pa-3"
> >
<h3 class="my-auto ml-4" v-text="page" v-show="!search" /> <h3 v-show="!search" class="my-auto ml-4" v-text="page" />
<v-btn icon v-if="search" class="mr-3 my-auto" @click="$emit('close-search')"> <v-btn
v-if="search"
icon
class="mr-3 my-auto"
@click="$emit('close-search')"
>
<v-icon>mdi-close</v-icon> <v-icon>mdi-close</v-icon>
</v-btn> </v-btn>
<v-text-field <v-text-field
v-if="search"
v-model="text"
solo solo
dense dense
flat flat
label="Search" label="Search"
v-model="text"
@input="$emit('text-changed', text)"
class="searchBar" class="searchBar"
v-if="search" @input="$emit('text-changed', text)"
v-on:keyup.enter="$emit('search-btn', text)" @keyup.enter="$emit('search-btn', text)"
/> />
<v-spacer v-if="!search" /> <v-spacer v-if="!search" />
@ -33,11 +38,11 @@
><v-icon>mdi-magnify</v-icon></v-btn ><v-icon>mdi-magnify</v-icon></v-btn
> >
<v-btn <v-btn
v-show="!search"
icon icon
tile tile
class="ml-4 mr-2 my-auto fill-height" class="ml-4 mr-2 my-auto fill-height"
style="border-radius: 0.25rem !important" style="border-radius: 0.25rem !important"
v-show="!search"
to="/settings" to="/settings"
><v-icon>mdi-dots-vertical</v-icon></v-btn ><v-icon>mdi-dots-vertical</v-icon></v-btn
> >
@ -46,7 +51,16 @@
<script> <script>
export default { export default {
props: ["search", "page"], props: {
search: {
type: Boolean,
default: false,
},
page: {
type: String,
default: "Home",
},
},
events: ["searchBtn", "textChanged", "closeSearch"], events: ["searchBtn", "textChanged", "closeSearch"],
data: () => ({ data: () => ({
text: "", text: "",

View file

@ -1,37 +1,47 @@
<template> <template>
<div> <div>
<v-snackbar v-model="updateSnackbar" class="updateBar" :timeout="updateSnackbarTimeout"> <v-snackbar
{{ updateSnackbarText }} v-model="updateSnackbar"
class="updateBar"
:timeout="updateSnackbarTimeout"
>
{{ updateSnackbarText }}
<template v-slot:action="{ attrs }"> <template #action="{ attrs }">
<v-btn color="primary" text v-bind="attrs" @click="updateSnackbar = false">Close</v-btn> <v-btn
</template> color="primary"
</v-snackbar> text
</div> v-bind="attrs"
@click="updateSnackbar = false"
>Close</v-btn
>
</template>
</v-snackbar>
</div>
</template> </template>
<style scoped> <style scoped>
.updateBar { .updateBar {
z-index: 99999999; z-index: 99999999;
} }
</style> </style>
<script> <script>
export default { export default {
data() { data() {
return { return {
updateSnackbar: false, updateSnackbar: false,
updateSnackbarText: "An update is available", updateSnackbarText: "An update is available",
updateSnackbarTimeout: 5000 updateSnackbarTimeout: 5000,
} };
}, },
async mounted() { async mounted() {
const commits = await this.$vuetube.commits; const commits = await this.$vuetube.commits;
const appVersion = process.env.appVersion; const appVersion = process.env.appVersion;
if (appVersion !== commits[0].sha && appVersion !== 'dev-local') { if (appVersion !== commits[0].sha && appVersion !== "dev-local") {
this.updateSnackbar = true; this.updateSnackbar = true;
} }
}, },
} };
</script> </script>

View file

@ -1,11 +1,11 @@
<template> <template>
<v-app v-show="stateLoaded" style="background: black !important"> <v-app v-show="stateLoaded" style="background: black !important">
<topNavigation <topNavigation
:search="search"
:page="page"
@close-search="search = !search" @close-search="search = !search"
@search-btn="searchBtn" @search-btn="searchBtn"
@text-changed="textChanged" @text-changed="textChanged"
:search="search"
:page="page"
/> />
<div class="accent2" style="height: 100%; margin-top: 4rem"> <div class="accent2" style="height: 100%; margin-top: 4rem">
@ -41,7 +41,7 @@
" "
> >
<div class="scroll-y" style="height: 100%"> <div class="scroll-y" style="height: 100%">
<div style="min-width: 180px" v-if="search"> <div v-if="search" style="min-width: 180px">
<v-list-item v-for="(item, index) in response" :key="index"> <v-list-item v-for="(item, index) in response" :key="index">
<v-icon>mdi-magnify</v-icon> <v-icon>mdi-magnify</v-icon>
<v-btn <v-btn
@ -65,8 +65,8 @@
<style> <style>
* { * {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Cantarell, "Open Sans", "Helvetica Neue", sans-serif; Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
} }
.scroll-y { .scroll-y {
overflow-y: scroll !important; /* has to be scroll, not auto */ overflow-y: scroll !important; /* has to be scroll, not auto */

View file

@ -1,6 +1,6 @@
<template> <template>
<v-app> <v-app>
<nuxt /> <nuxt />
</v-app> </v-app>
</template> </template>
@ -8,4 +8,4 @@
* { * {
font-family: Arial, Helvetica, sans-serif !important; font-family: Arial, Helvetica, sans-serif !important;
} }
</style> </style>

View file

@ -1,31 +1,28 @@
<template> <template>
<v-app> <v-app>
<center> <center>
<v-icon size="100">mdi-alert-circle</v-icon> <v-icon size="100">mdi-alert-circle</v-icon>
<h1 class="grey--text">An error occured!</h1> <h1 class="grey--text">An error occured!</h1>
<v-btn to="/">Reload Application</v-btn> <v-btn to="/">Reload Application</v-btn>
<v-btn to="/logs">Show Logs</v-btn> <v-btn to="/logs">Show Logs</v-btn>
<div style="margin-top: 5em; color: #999; font-size: 0.75em">
<div style="margin-top: 5em; color: #999; font-size: 0.75em;"> <div style="font-size: 1.4em">Error Information</div>
<div style="font-size: 1.4em;">Error Information</div>
<div>Code: {{ error.statusCode }}</div> <div>Code: {{ error.statusCode }}</div>
<div>Path: {{ this.$route.fullPath }}</div> <div>Path: {{ $route.fullPath }}</div>
</div> </div>
</center> </center>
</v-app> </v-app>
</template> </template>
<script> <script>
export default { export default {
layout: 'empty', layout: "empty",
props: { props: {
error: { error: {
type: Object, type: Object,
default: null default: null,
} },
} },
} };
</script> </script>

View file

@ -1,59 +1,54 @@
import colors from 'vuetify/es5/util/colors' import colors from "vuetify/es5/util/colors";
/**** Front's Notes / Don't Remove **** /**** Front's Notes / Don't Remove ****
* Data Storage: * Data Storage:
* localStorage.setItem("key", data) * localStorage.setItem("key", data)
* localStorage.getItem('key') * localStorage.getItem('key')
*/ */
export default { export default {
//--- Bettertube Stuff ---// //--- Bettertube Stuff ---//
env: { env: {
appVersion: "dev-local", appVersion: "dev-local",
}, },
target: 'static', target: "static",
plugins: [ plugins: [
{ src: "~/plugins/youtube", mode: "client" }, { src: "~/plugins/youtube", mode: "client" },
{ src: "~/plugins/vuetube", mode: "client" }, { src: "~/plugins/vuetube", mode: "client" },
{ src: "~/plugins/ryd", mode: "client"} { src: "~/plugins/ryd", mode: "client" },
], ],
generate: { generate: {
dir: '../dist' dir: "../dist",
}, },
//--- Bettertube Debugging ---// //--- Bettertube Debugging ---//
server: { server: {
port: 80, // default: 3000 (Note: Running on ports below 1024 requires root privileges!) port: 80, // default: 3000 (Note: Running on ports below 1024 requires root privileges!)
host: '0.0.0.0', // default: localhost, host: "0.0.0.0", // default: localhost,
timing: false timing: false,
}, },
//--- Default NUXT Stuff ---// //--- Default NUXT Stuff ---//
head: { head: {
title: 'VueTube', title: "VueTube",
htmlAttrs: { htmlAttrs: {
lang: 'en' lang: "en",
}, },
meta: [ meta: [
{ charset: 'utf-8' }, { charset: "utf-8" },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }, { name: "viewport", content: "width=device-width, initial-scale=1" },
{ name: 'format-detection', content: 'telephone=no' } { name: "format-detection", content: "telephone=no" },
] ],
}, },
css: [], css: [],
components: true, components: true,
buildModules: [ buildModules: ["@nuxtjs/vuetify"],
'@nuxtjs/vuetify',
],
modules: [], modules: [],
vuetify: { vuetify: {
customVariables: ['~/assets/variables.scss'], customVariables: ["~/assets/variables.scss"],
theme: { theme: {
dark: false, dark: false,
options: { customProperties: true }, options: { customProperties: true },
@ -64,7 +59,7 @@ export default {
accent: "#CD201F", accent: "#CD201F",
accent2: "#fff", accent2: "#fff",
background: "#fff", background: "#fff",
info: "#000" info: "#000",
}, },
dark: { dark: {
primary: colors.red.darken2, //colors.blue.darken2 primary: colors.red.darken2, //colors.blue.darken2
@ -73,8 +68,8 @@ export default {
accent2: "#222", accent2: "#222",
background: "#333", background: "#333",
info: "#fff", info: "#fff",
} },
} },
} },
} },
} };

View file

@ -26,4 +26,4 @@ export default {
.catch((error) => this.$logger("Home Page", error, true)); .catch((error) => this.$logger("Home Page", error, true));
}, },
}; };
</script> </script>

View file

@ -1,6 +1,10 @@
<template> <template>
<center> <center>
<v-img contain style="margin-top: 5em; max-width: 80%; max-height: 15em;" src="/dev.svg" /> <v-img
contain
style="margin-top: 5em; max-width: 80%; max-height: 15em"
src="/dev.svg"
/>
<h1 class="grey--text">Page Under Construction</h1> <h1 class="grey--text">Page Under Construction</h1>
<p class="grey--text">Please read the VueTube FAQ for more information.</p> <p class="grey--text">Please read the VueTube FAQ for more information.</p>
</center> </center>

View file

@ -1,22 +1,25 @@
<template> <template>
<center style="padding: 1em;"> <center style="padding: 1em">
<div class="row pa-4" style="flex-direction: column"> <div class="row pa-4" style="flex-direction: column">
<div> <div>
<v-img src="/icon.svg" width="100px"/> <v-img src="/icon.svg" width="100px" />
</div> </div>
<v-spacer/> <v-spacer />
<div> <div>
<h1 class="pageTitle mb-3">VueTube</h1> <h1 class="pageTitle mb-3">VueTube</h1>
<v-btn @click="openExternal('https://github.com/Frontesque/VueTube')"><v-icon>mdi-github</v-icon></v-btn> <v-btn @click="openExternal('https://github.com/Frontesque/VueTube')"
<v-btn @click="openExternal('https://discord.gg/7P8KJrdd5W')"><v-icon>mdi-discord</v-icon></v-btn> ><v-icon>mdi-github</v-icon></v-btn
>
<v-btn @click="openExternal('https://discord.gg/7P8KJrdd5W')"
><v-icon>mdi-discord</v-icon></v-btn
>
</div> </div>
</div> </div>
<h3 style="margin-top: 2em;">App Information</h3> <h3 style="margin-top: 2em">App Information</h3>
<div>App Version: {{ version.substring(0, 7) }}</div> <div>App Version: {{ version.substring(0, 7) }}</div>
<h3 style="margin-top: 1em;">Device Information</h3> <h3 style="margin-top: 1em">Device Information</h3>
<div>Platform: {{ deviceInfo.platform }}</div> <div>Platform: {{ deviceInfo.platform }}</div>
<div>OS: {{ deviceInfo.operatingSystem }} ({{ deviceInfo.osVersion }})</div> <div>OS: {{ deviceInfo.operatingSystem }} ({{ deviceInfo.osVersion }})</div>
<div>Model: {{ deviceInfo.model }}</div> <div>Model: {{ deviceInfo.model }}</div>
@ -32,25 +35,25 @@
</style> </style>
<script> <script>
import { Browser } from '@capacitor/browser'; import { Browser } from "@capacitor/browser";
import { Device } from '@capacitor/device'; import { Device } from "@capacitor/device";
export default { export default {
data() { data() {
return { return {
version: process.env.appVersion, version: process.env.appVersion,
deviceInfo: "", deviceInfo: "",
} };
},
async mounted() {
const info = await Device.getInfo();
this.deviceInfo = info;
}, },
methods: { methods: {
async openExternal(url) { async openExternal(url) {
await Browser.open({ url: url }); await Browser.open({ url: url });
} },
}, },
};
async mounted () {
const info = await Device.getInfo();
this.deviceInfo = info
}
}
</script> </script>

View file

@ -1,24 +1,26 @@
<template> <template>
<div> <div>
<v-list-item v-for="(item, index) in logs" :key="index"> <v-list-item v-for="(item, index) in logs" :key="index">
<v-card class="card"> <v-card class="card">
<v-card-title> <v-card-title>
<v-chip outlined class="errorChip" color="error" v-if="item.error">Error</v-chip> <v-chip v-if="item.error" outlined class="errorChip" color="error"
>Error</v-chip
>
{{ item.name }} {{ item.name }}
<span v-text="`• ${new Date(item.time).toLocaleString()}`" class="date" /> <span
class="date"
v-text="`• ${new Date(item.time).toLocaleString()}`"
/>
</v-card-title> </v-card-title>
<v-expansion-panels> <v-expansion-panels>
<v-expansion-panel> <v-expansion-panel>
<v-expansion-panel-header>More</v-expansion-panel-header> <v-expansion-panel-header>More</v-expansion-panel-header>
<v-expansion-panel-content v-text="item.data" class="logContent" /> <v-expansion-panel-content class="logContent" v-text="item.data" />
</v-expansion-panel> </v-expansion-panel>
</v-expansion-panels> </v-expansion-panels>
</v-card> </v-card>
</v-list-item> </v-list-item>
</div> </div>
</template> </template>
@ -46,12 +48,12 @@
export default { export default {
data() { data() {
return { return {
logs: new Array() logs: new Array(),
} };
}, },
mounted() { mounted() {
const logs = this.$youtube.logs const logs = this.$youtube.logs;
this.logs = logs; this.logs = logs;
} },
} };
</script> </script>

View file

@ -1,50 +1,38 @@
<template> <template>
<div class="mainContainer pt-1"> <div class="mainContainer pt-1">
<v-card class="pb-5"> <v-card class="pb-5">
<v-card-title>Default Page</v-card-title> <v-card-title>Default Page</v-card-title>
<v-card-text> <v-card-text>
<v-select <v-select
:items="pages"
v-model="page" v-model="page"
:items="pages"
label="Default Page" label="Default Page"
solo solo
></v-select> ></v-select>
</v-card-text> </v-card-text>
</v-card> </v-card>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
page: "home", page: "home",
pages: [ pages: ["home", "subscriptions", "library"],
"home", };
"subscriptions", },
"library"
] watch: {
} page: function (newVal) {
localStorage.setItem("startPage", newVal);
},
}, },
mounted() { mounted() {
this.page = localStorage.getItem("startPage") || "home"; this.page = localStorage.getItem("startPage") || "home";
}, },
};
watch: {
page: function (val, oldVal) {
localStorage.setItem("startPage", val);
}
}
}
</script> </script>
<style scoped> <style scoped>

View file

@ -1,6 +1,5 @@
<template> <template>
<div class="py-1"> <div class="py-1">
<v-card class="pb-5"> <v-card class="pb-5">
<v-card-title>Global Base Color</v-card-title> <v-card-title>Global Base Color</v-card-title>
<v-row class="ml-3 mr-6"> <v-row class="ml-3 mr-6">
@ -14,13 +13,25 @@
@click="saveTheme($vuetify.theme.dark)" @click="saveTheme($vuetify.theme.dark)"
/> />
</section> </section>
<v-btn v-if="$vuetify.theme.dark" text tile class="white--text black" @click="amoled" > <v-btn
v-if="$vuetify.theme.dark"
text
tile
class="white--text black"
@click="amoled"
>
{{ {{
this.$vuetify.theme.themes.dark.background === '#000' $vuetify.theme.themes.dark.background === "#000" ? "LCD" : "OLED"
? 'LCD'
: 'OLED'
}} }}
<v-icon :size="this.$vuetify.theme.themes.dark.background === '#000' ? '.5rem' : '.9rem'" class="ml-2">mdi-brightness-2</v-icon> <v-icon
:size="
$vuetify.theme.themes.dark.background === '#000'
? '.5rem'
: '.9rem'
"
class="ml-2"
>mdi-brightness-2</v-icon
>
</v-btn> </v-btn>
</v-row> </v-row>
</v-card> </v-card>
@ -28,64 +39,35 @@
<v-card class="pb-5"> <v-card class="pb-5">
<v-card-title>Accent Color</v-card-title> <v-card-title>Accent Color</v-card-title>
<v-card-text> <v-card-text>
<v-alert color="primary" dense outlined type="warning"
<v-alert color="primary" dense outlined type="warning">NOTE: This doesn't save after closing the app (yet)</v-alert> >NOTE: This doesn't save after closing the app (yet)</v-alert
<v-color-picker dot-size="5" hide-mode-switch mode="hexa" v-model="accentColor" /> >
<v-color-picker
v-model="accentColor"
dot-size="5"
hide-mode-switch
mode="hexa"
/>
</v-card-text> </v-card-text>
</v-card> </v-card>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
accentColor: '#ffffff' accentColor: "#ffffff",
} };
},
mounted() {
this.accentColor = this.$vuetify.theme.themes.dark.primary;
this.roblox = localStorage.getItem('roblox') === 'true';
},
methods: {
amoled() {
this.$vuetify.theme.themes.dark.background === '#000' ? (
this.$vuetify.theme.themes.dark.accent = '#222',
this.$vuetify.theme.themes.dark.accent2 = '#222',
this.$vuetify.theme.themes.dark.background = '#333',
localStorage.setItem("isOled", false)
) : (
this.$vuetify.theme.themes.dark.accent = '#000',
this.$vuetify.theme.themes.dark.accent2 = '#000',
this.$vuetify.theme.themes.dark.background = '#000',
localStorage.setItem("isOled", true)
)
// doesn't work 😭
// console.log(document.getElementsByClassName('v-application--wrap')[0])
// console.log(document.getElementsByClassName('v-application--wrap')[0].style)
// document.getElementsByClassName('v-application--wrap')[0].style.backgroundColor = "#000000 !important"
},
saveTheme(isDark) {
this.$vuetube.statusBar.setBackground(this.$vuetify.theme.currentTheme.accent)
localStorage.setItem("darkTheme", isDark);
},
}, },
watch: { watch: {
accentColor: function (val, oldVal) { accentColor: function (val) {
this.$vuetify.theme.currentTheme.primary = val; this.$vuetify.theme.currentTheme.primary = val;
let primaryAlt = this.$vuetube.hexToRgb(val); let primaryAlt = this.$vuetube.hexToRgb(val);
let rgbEdit = 130; //Light Mode let rgbEdit = 130; //Light Mode
if (localStorage.getItem('darkTheme') === "true") rgbEdit = -80; //Dark Mode if (localStorage.getItem("darkTheme") === "true") rgbEdit = -80; //Dark Mode
for (const i in primaryAlt) { for (const i in primaryAlt) {
primaryAlt[i] = primaryAlt[i] + rgbEdit; //Amount To Lighten By primaryAlt[i] = primaryAlt[i] + rgbEdit; //Amount To Lighten By
@ -93,15 +75,45 @@ export default {
if (primaryAlt[i] < 0) primaryAlt[i] = 0; if (primaryAlt[i] < 0) primaryAlt[i] = 0;
} }
primaryAlt = this.$vuetube.rgbToHex(primaryAlt.r, primaryAlt.g, primaryAlt.b); primaryAlt = this.$vuetube.rgbToHex(
primaryAlt.r,
primaryAlt.g,
primaryAlt.b
);
this.$vuetify.theme.currentTheme.primaryAlt = primaryAlt; this.$vuetify.theme.currentTheme.primaryAlt = primaryAlt;
} },
} },
} mounted() {
this.accentColor = this.$vuetify.theme.themes.dark.primary;
this.roblox = localStorage.getItem("roblox") === "true";
},
methods: {
amoled() {
this.$vuetify.theme.themes.dark.background === "#000"
? ((this.$vuetify.theme.themes.dark.accent = "#222"),
(this.$vuetify.theme.themes.dark.accent2 = "#222"),
(this.$vuetify.theme.themes.dark.background = "#333"),
localStorage.setItem("isOled", false))
: ((this.$vuetify.theme.themes.dark.accent = "#000"),
(this.$vuetify.theme.themes.dark.accent2 = "#000"),
(this.$vuetify.theme.themes.dark.background = "#000"),
localStorage.setItem("isOled", true));
// doesn't work 😭
// console.log(document.getElementsByClassName('v-application--wrap')[0])
// console.log(document.getElementsByClassName('v-application--wrap')[0].style)
// document.getElementsByClassName('v-application--wrap')[0].style.backgroundColor = "#000000 !important"
},
saveTheme(isDark) {
this.$vuetube.statusBar.setBackground(
this.$vuetify.theme.currentTheme.accent
);
localStorage.setItem("darkTheme", isDark);
},
},
};
</script> </script>
<style scoped> <style scoped>

View file

@ -18,14 +18,14 @@
thumb-size="64" thumb-size="64"
></v-slider> ></v-slider>
<v-slider <v-slider
v-model="roundTweak"
class="mr-2" class="mr-2"
label="Inner" label="Inner"
v-model="roundTweak"
:max="4" :max="4"
step="1" step="1"
thumb-size="64" thumb-size="64"
> >
<template v-slot:thumb-label="{ value }"> <template #thumb-label="{ value }">
<div <div
class="pa-4 white text-red red-text red--text" class="pa-4 white text-red red-text red--text"
:style="{ borderRadius: value * 3 + 'px !important' }" :style="{ borderRadius: value * 3 + 'px !important' }"

View file

@ -1,30 +1,42 @@
<template> <template>
<div class="py-2"> <div class="py-2">
<v-list-item v-for="(item, index) in commits" :key="index" class="my-1"> <v-list-item v-for="(item, index) in commits" :key="index" class="my-1">
<v-card class="card my-2"> <v-card class="card my-2">
<v-card-title style="padding: 0 0.25em 0 0.75em;"> <v-card-title style="padding: 0 0.25em 0 0.75em">
{{ item.author ? item.author.login : item.commit.author.name }} {{ item.author ? item.author.login : item.commit.author.name }}
<span v-text="`• ${item.sha.substring(0, 7)}`" class="subtitle" /> <span class="subtitle" v-text="`• ${item.sha.substring(0, 7)}`" />
<v-spacer /> <v-spacer />
<v-chip outlined class="tags" color="orange" v-if="index == 0">Latest</v-chip> <v-chip v-if="index == 0" outlined class="tags" color="orange"
<v-chip outlined class="tags" color="green" v-if="item.sha == installedVersion">Installed</v-chip> >Latest</v-chip
>
<v-chip
v-if="item.sha == installedVersion"
outlined
class="tags"
color="green"
>Installed</v-chip
>
</v-card-title> </v-card-title>
<div style="margin-left: 1em;"> <div style="margin-left: 1em">
<div class="date" v-text="new Date(item.commit.committer.date).toLocaleString()" /> <div
class="date"
v-text="new Date(item.commit.committer.date).toLocaleString()"
/>
{{ item.commit.message }} {{ item.commit.message }}
</div> </div>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer />
<v-btn @click="openExternal(item)"><v-icon class="btn-icon">mdi-github</v-icon>View</v-btn> <v-btn @click="openExternal(item)"
<v-btn disabled @click="install(item)"><v-icon class="btn-icon">mdi-download</v-icon>Install</v-btn> ><v-icon class="btn-icon">mdi-github</v-icon>View</v-btn
>
<v-btn disabled @click="install(item)"
><v-icon class="btn-icon">mdi-download</v-icon>Install</v-btn
>
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-list-item> </v-list-item>
</div> </div>
</template> </template>
@ -51,21 +63,22 @@
</style> </style>
<script> <script>
import { Browser } from '@capacitor/browser'; import { Browser } from "@capacitor/browser";
export default { export default {
data() { data() {
return { return {
commits: new Array(), commits: new Array(),
installedVersion: process.env.appVersion installedVersion: process.env.appVersion,
} };
}, },
async mounted() { async mounted() {
const commits = await this.$vuetube.commits; const commits = await this.$vuetube.commits;
if (commits[0].sha) { //If Commit Valid if (commits[0].sha) {
//If Commit Valid
this.commits = commits; this.commits = commits;
} else { } else {
console.log(commits) console.log(commits);
} }
}, },
methods: { methods: {
@ -75,10 +88,9 @@ export default {
install(item) { install(item) {
this.$vuetube.getRuns(item, (data) => { this.$vuetube.getRuns(item, (data) => {
console.log(data) console.log(data);
}); });
},
} },
} };
}
</script> </script>

View file

@ -10,9 +10,13 @@
<v-card-text> <v-card-text>
<div style="position: relative"> <div style="position: relative">
<v-img :src="video.thumbnails[video.thumbnails.length - 1].url" /> <v-img :src="video.thumbnails[video.thumbnails.length - 1].url" />
<div v-text="video.runtime" class="videoRuntimeFloat" style="color: #fff" /> <div
class="videoRuntimeFloat"
style="color: #fff"
v-text="video.runtime"
/>
</div> </div>
<div v-text="video.title" style="margin-top: 0.5em" /> <div style="margin-top: 0.5em" v-text="video.title" />
<div v-text="`${video.views} • ${video.uploaded}`" /> <div v-text="`${video.views} • ${video.uploaded}`" />
</v-card-text> </v-card-text>
</v-card> </v-card>

View file

@ -1,14 +1,17 @@
<template> <template>
<div style="padding-top: 1em;"> <div style="padding-top: 1em">
<v-list-item v-for="(item, index) in settingsItems" :key="index"> <v-list-item v-for="(item, index) in settingsItems" :key="index">
<v-btn
<v-btn text class="entry text-left text-capitalize" :to="item.to" :disabled="item.disabled"> text
<v-icon v-text="item.icon" size="30px" class="icon" /> class="entry text-left text-capitalize"
:to="item.to"
:disabled="item.disabled"
>
<v-icon size="30px" class="icon" v-text="item.icon" />
{{ item.name }} {{ item.name }}
</v-btn> </v-btn>
</v-list-item> </v-list-item>
</div> </div>
</template> </template>
<style scoped> <style scoped>
@ -30,15 +33,28 @@ export default {
settingsItems: [ settingsItems: [
{ name: "General", icon: "mdi-cog", to: "", disabled: true }, { name: "General", icon: "mdi-cog", to: "", disabled: true },
{ name: "Theme", icon: "mdi-brush-variant", to: "/mods/theme" }, { name: "Theme", icon: "mdi-brush-variant", to: "/mods/theme" },
{ name: "Player", icon: "mdi-motion-play-outline", to: "", disabled: true }, {
{ name: "UI Tweaker", icon: "mdi-television-guide", to: "/mods/tweaks" }, name: "Player",
icon: "mdi-motion-play-outline",
to: "",
disabled: true,
},
{
name: "UI Tweaker",
icon: "mdi-television-guide",
to: "/mods/tweaks",
},
{ name: "Startup Options", icon: "mdi-restart", to: "/mods/startup" }, { name: "Startup Options", icon: "mdi-restart", to: "/mods/startup" },
{ name: "Plugins", icon: "mdi-puzzle", to: "", disabled: true}, { name: "Plugins", icon: "mdi-puzzle", to: "", disabled: true },
{ name: "Updates", icon: "mdi-cloud-download-outline", to: "/mods/updates" }, {
name: "Updates",
icon: "mdi-cloud-download-outline",
to: "/mods/updates",
},
{ name: "Logs", icon: "mdi-text-box-outline", to: "/mods/logs" }, { name: "Logs", icon: "mdi-text-box-outline", to: "/mods/logs" },
{ name: "About", icon: "mdi-information-outline", to: "/mods/about" }, { name: "About", icon: "mdi-information-outline", to: "/mods/about" },
] ],
} };
} },
} };
</script> </script>

View file

@ -1,6 +1,10 @@
<template> <template>
<center> <center>
<v-img contain style="margin-top: 5em; max-width: 80%; max-height: 15em;" src="/dev.svg" /> <v-img
contain
style="margin-top: 5em; max-width: 80%; max-height: 15em"
src="/dev.svg"
/>
<h1 class="grey--text">Page Under Construction</h1> <h1 class="grey--text">Page Under Construction</h1>
<p class="grey--text">Please read the VueTube FAQ for more information.</p> <p class="grey--text">Please read the VueTube FAQ for more information.</p>
</center> </center>

View file

@ -18,11 +18,11 @@
> >
<v-btn <v-btn
text text
@click="item.action"
class="vertical-button" class="vertical-button"
style="padding: 0; margin: 0" style="padding: 0; margin: 0"
elevation="0" elevation="0"
:disabled="item.disabled" :disabled="item.disabled"
@click="item.action"
> >
<v-icon v-text="item.icon" /> <v-icon v-text="item.icon" />
<div v-text="item.value || item.name" /> <div v-text="item.value || item.name" />
@ -40,7 +40,7 @@
<p>Channel Stuff</p> <p>Channel Stuff</p>
<hr /> <hr />
</v-card-text> </v-card-text>
<div class="scroll-y ml-2 mr-2" v-if="showMore"> <div v-if="showMore" class="scroll-y ml-2 mr-2">
{{ description }} {{ description }}
</div> </div>
@ -72,7 +72,6 @@
</div> </div>
</template> </template>
<style> <style>
.vertical-button span.v-btn__content { .vertical-button span.v-btn__content {
flex-direction: column; flex-direction: column;
@ -82,12 +81,6 @@
<script> <script>
export default { export default {
methods: {
dislike() {},
share() {
this.share = !this.share;
},
},
data() { data() {
return { return {
interactions: [ interactions: [
@ -151,5 +144,11 @@ export default {
this.interactions[1].value = data.dislikes.toLocaleString(); this.interactions[1].value = data.dislikes.toLocaleString();
}); });
}, },
methods: {
dislike() {},
share() {
this.share = !this.share;
},
},
}; };
</script> </script>

View file

@ -1,67 +1,67 @@
// To centralize certain values and URLs as for easier debugging and refactoring // To centralize certain values and URLs as for easier debugging and refactoring
const url = { const url = {
YT_URL: 'https://www.youtube.com', YT_URL: "https://www.youtube.com",
YT_MOBILE: "https://m.youtube.com", YT_MOBILE: "https://m.youtube.com",
YT_MUSIC_URL: 'https://music.youtube.com', YT_MUSIC_URL: "https://music.youtube.com",
YT_BASE_API: 'https://www.youtube.com/youtubei/v1', YT_BASE_API: "https://www.youtube.com/youtubei/v1",
YT_SUGGESTIONS: "https://suggestqueries.google.com/complete", YT_SUGGESTIONS: "https://suggestqueries.google.com/complete",
VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube", VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube",
} };
const ytApiVal = { const ytApiVal = {
VERSION: "16.25", VERSION: "16.25",
CLIENTNAME: "ANDROID", CLIENTNAME: "ANDROID",
VERSION_WEB: "2.20220318.00.00", VERSION_WEB: "2.20220318.00.00",
CLIENT_WEB: 2 CLIENT_WEB: 2,
} };
module.exports = { module.exports = {
URLS: url, URLS: url,
YT_API_VALUES: ytApiVal, YT_API_VALUES: ytApiVal,
LOGGER_NAMES: { LOGGER_NAMES: {
search: "Search", search: "Search",
autoComplete: "AutoComplete", autoComplete: "AutoComplete",
watch: "Watch", watch: "Watch",
recommendations: "Recommendations", recommendations: "Recommendations",
init: "Initialize", init: "Initialize",
innertube: "Innertube" innertube: "Innertube",
}, },
INNERTUBE_HEADER: (info) => { INNERTUBE_HEADER: (info) => {
let headers = { let headers = {
accept: '*/*', accept: "*/*",
'user-agent': info.userAgent, "user-agent": info.userAgent,
'accept-language': `${info.hl}-${info.gl},${info.hl};q=0.9`, "accept-language": `${info.hl}-${info.gl},${info.hl};q=0.9`,
'content-type': 'application/json', "content-type": "application/json",
'x-goog-authuser': 0, "x-goog-authuser": 0,
'x-goog-visitor-id': info.visitorData || "", "x-goog-visitor-id": info.visitorData || "",
'x-youtube-client-name': ytApiVal.CLIENTNAME, "x-youtube-client-name": ytApiVal.CLIENTNAME,
'x-youtube-client-version': ytApiVal.VERSION, "x-youtube-client-version": ytApiVal.VERSION,
'x-origin': info.originalUrl, "x-origin": info.originalUrl,
'origin': info.originalUrl, origin: info.originalUrl,
}; };
return headers return headers;
}, },
INNERTUBE_CLIENT: (info) => { INNERTUBE_CLIENT: (info) => {
let client = { let client = {
"gl": info.gl, gl: info.gl,
"hl": info.hl, hl: info.hl,
"deviceMake": info.deviceMake, deviceMake: info.deviceMake,
"deviceModel": info.deviceModel, deviceModel: info.deviceModel,
"userAgent": info.userAgent, userAgent: info.userAgent,
"clientName": ytApiVal.CLIENTNAME, clientName: ytApiVal.CLIENTNAME,
"clientVersion": ytApiVal.VERSION, clientVersion: ytApiVal.VERSION,
"osName": info.osName, osName: info.osName,
"osVersion": info.osVersion, osVersion: info.osVersion,
"platform": "MOBILE", platform: "MOBILE",
"originalUrl": info.originalUrl, originalUrl: info.originalUrl,
"configInfo": info.configInfo, configInfo: info.configInfo,
"remoteHost": info.remoteHost, remoteHost: info.remoteHost,
"visitorData": info.visitorData, visitorData: info.visitorData,
}; };
return client return client;
}, },
} };

View file

@ -1,180 +1,217 @@
// Code specific to working with the innertube API // Code specific to working with the innertube API
// https://www.youtube.com/youtubei/v1 // https://www.youtube.com/youtubei/v1
import { Http } from '@capacitor-community/http'; import { Http } from "@capacitor-community/http";
import { getBetweenStrings } from './utils'; import { getBetweenStrings } from "./utils";
import constants from './constants'; import constants from "./constants";
class Innertube { class Innertube {
//--- Initiation ---//
//--- Initiation ---// constructor(ErrorCallback) {
this.ErrorCallback = ErrorCallback || undefined;
this.retry_count = 0;
}
constructor(ErrorCallback) { checkErrorCallback() {
this.ErrorCallback = ErrorCallback || undefined; return typeof this.ErrorCallback === "function";
this.retry_count = 0 }
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.logged_in = data.LOGGED_IN;
this.context.client = constants.INNERTUBE_CLIENT(this.context.client);
this.header = constants.INNERTUBE_HEADER(this.context.client);
}
} 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 async createAsync(ErrorCallback) {
const created = new Innertube(ErrorCallback);
await created.initAsync();
return created;
}
//--- API Calls ---//
async browseAsync(action_type) {
let data = { context: this.context };
switch (action_type) {
case "recommendations":
data.browseId = "FEwhat_to_watch";
break;
case "playlist":
data.browseId = args.browse_id;
break;
default:
} }
checkErrorCallback() { console.log(data);
return typeof this.ErrorCallback === "function"
}
async initAsync() { const response = await Http.post({
const html = await Http.get({ url: constants.URLS.YT_URL, params: { hl: "en" } }).catch((error) => error); url: `${constants.URLS.YT_BASE_API}/browse?key=${this.key}`,
try { data: data,
if (html instanceof Error && this.checkErrorCallback) this.ErrorCallback(html.message, true); headers: { "Content-Type": "application/json" },
try { }).catch((error) => error);
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.logged_in = data.LOGGED_IN;
this.context.client = constants.INNERTUBE_CLIENT(this.context.client) if (response instanceof Error)
this.header = constants.INNERTUBE_HEADER(this.context.client) return {
} success: false,
status_code: response.status,
message: response.message,
};
} catch (err) { return {
console.log(err) success: true,
if (this.checkErrorCallback) this.ErrorCallback(err, true) status_code: response.status,
if (this.retry_count >= 10) { this.initAsync() } else { if (this.checkErrorCallback) this.ErrorCallback("Failed to retrieve Innertube session", true); } data: response.data,
}
} catch (error) {
this.ErrorCallback(error, true)
};
}; };
}
static async createAsync(ErrorCallback) { static getThumbnail(id, resolution) {
const created = new Innertube(ErrorCallback); if (resolution == "max") {
await created.initAsync(); const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg`;
return created; 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`;
}
//--- API Calls ---// async getVidAsync(id) {
let data = { context: this.context, videoId: id };
const response = await Http.get({
url: `https://m.youtube.com/watch?v=${id}&pbj=1`,
params: {},
headers: Object.assign(this.header, {
referer: `https://m.youtube.com/watch?v=${id}`,
"x-youtube-client-name": constants.YT_API_VALUES.CLIENT_WEB,
"x-youtube-client-version": constants.YT_API_VALUES.VERSION_WEB,
}),
}).catch((error) => error);
async browseAsync(action_type) { const responseMobile = await Http.post({
let data = { context: this.context } url: `${constants.URLS.YT_BASE_API}/player?key=${this.key}`,
data: data,
headers: constants.INNERTUBE_HEADER(this.context),
}).catch((error) => error);
switch (action_type) { if (response instanceof Error)
case 'recommendations': return {
data.browseId = 'FEwhat_to_watch' success: false,
break; status_code: response.response.status,
case 'playlist': message: response.message,
data.browseId = args.browse_id };
break;
default:
}
console.log(data) return {
success: true,
status_code: response.status,
data: { webOutput: response.data, appOutput: responseMobile.data },
};
}
const response = await Http.post({ // Simple Wrappers
url: `${constants.URLS.YT_BASE_API}/browse?key=${this.key}`, async getRecommendationsAsync() {
data: data, const rec = await this.browseAsync("recommendations");
headers: { "Content-Type": "application/json" } console.log(rec.data);
}).catch((error) => error); return rec;
}
if (response instanceof Error) return { success: false, status_code: response.status, message: response.message }; async VidInfoAsync(id) {
let response = await this.getVidAsync(id);
return { if (
success: true, response.success &&
status_code: response.status, response.data.webOutput[2].playerResponse?.playabilityStatus?.status ==
data: response.data ("ERROR" || undefined)
}; )
} throw new Error(
`Could not get information for video: ${response[2].playerResponse?.playabilityStatus?.status} - ${response[2].playerResponse?.playabilityStatus?.reason}`
static getThumbnail(id, resolution) { );
if (resolution == "max"){ const responseWeb = response.data.webOutput;
const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg` const responseApp = response.data.appOutput;
let img = new Image(); const details = responseWeb[2].playerResponse?.videoDetails;
img.src = url const microformat =
img.onload = function(){ responseWeb[2].playerResponse?.microformat?.playerMicroformatRenderer;
if (img.height !== 120) return url const renderedPanels = responseWeb[3].response?.engagementPanels;
}; const columnUI =
} responseWeb[3].response?.contents.singleColumnWatchNextResults?.results
return `https://img.youtube.com/vi/${id}/mqdefault.jpg` ?.results;
} const resolutions = responseApp.streamingData;
async getVidAsync(id) {
let data = { context: this.context, videoId: id }
const response = await Http.get({
url: `https://m.youtube.com/watch?v=${id}&pbj=1`,
params: {},
headers: Object.assign(this.header, {
referer: `https://m.youtube.com/watch?v=${id}`,
'x-youtube-client-name': constants.YT_API_VALUES.CLIENT_WEB,
'x-youtube-client-version': constants.YT_API_VALUES.VERSION_WEB})
}).catch((error) => error);
const responseMobile = await Http.post({
url: `${constants.URLS.YT_BASE_API}/player?key=${this.key}`,
data: data,
headers: constants.INNERTUBE_HEADER(this.context)
}).catch((error) => error);
if (response instanceof Error) return { success: false, status_code: response.response.status, message: response.message };
return {
success: true,
status_code: response.status,
data: {webOutput: response.data, appOutput: responseMobile.data}
};
}
// Simple Wrappers
async getRecommendationsAsync() {
const rec = await this.browseAsync("recommendations");
console.log(rec.data)
return rec;
}
async VidInfoAsync(id) {
let response = await this.getVidAsync(id)
if (response.success && (response.data.webOutput[2].playerResponse?.playabilityStatus?.status == ("ERROR" || undefined)))
throw new Error(`Could not get information for video: ${response[2].playerResponse?.playabilityStatus?.status} - ${response[2].playerResponse?.playabilityStatus?.reason}`)
const responseWeb = response.data.webOutput
const responseApp = response.data.appOutput
const details = responseWeb[2].playerResponse?.videoDetails
const microformat = responseWeb[2].playerResponse?.microformat?.playerMicroformatRenderer
const renderedPanels = responseWeb[3].response?.engagementPanels
const columnUI = responseWeb[3].response?.contents.singleColumnWatchNextResults?.results?.results
const resolutions = responseApp.streamingData
console.log((columnUI.contents).length)
return {
id: details.videoId,
title: details.title || microformat.title?.runs[0].text,
isLive: details.isLiveContent || microformat.liveBroadcastDetails?.isLiveNow || false,
channelName: details.author || microformat.ownerChannelName,
channelUrl: microformat.ownerProfileUrl,
availableResolutions: resolutions?.formats,
availableResolutionsAdaptive: resolutions?.adaptiveFormats,
metadata: {
description: microformat.description?.runs[0].text,
descriptionShort: details.shortDescription,
thumbnails: details.thumbnails?.thumbnails || microformat.thumbnails?.thumbnails,
isFamilySafe: microformat.isFamilySafe,
availableCountries: microformat.availableCountries,
liveBroadcastDetails: microformat.liveBroadcastDetails,
uploadDate: microformat.uploadDate,
publishDate: microformat.publishDate,
isPrivate: details.isPrivate,
viewCount: details.viewCount || microformat.viewCount,
lengthSeconds: details.lengthSeconds || microformat.lengthSeconds,
likes: parseInt(columnUI?.contents[1]
.slimVideoMetadataSectionRenderer?.contents[1].slimVideoActionBarRenderer?.buttons[0]
.slimMetadataToggleButtonRenderer?.button?.toggleButtonRenderer?.defaultText?.accessibility?.accessibilityData?.label?.replace(/\D/g, '')) // Yes. I know.
},
renderedData: {
description: renderedPanels[0].engagementPanelSectionListRenderer?.content.structuredDescriptionContentRenderer?.items[1].expandableVideoDescriptionBodyRenderer?.descriptionBodyText.runs,
recommendations: columnUI?.contents[(columnUI.contents).length -1].itemSectionRenderer?.contents
}
}
}
console.log(columnUI.contents.length);
return {
id: details.videoId,
title: details.title || microformat.title?.runs[0].text,
isLive:
details.isLiveContent ||
microformat.liveBroadcastDetails?.isLiveNow ||
false,
channelName: details.author || microformat.ownerChannelName,
channelUrl: microformat.ownerProfileUrl,
availableResolutions: resolutions?.formats,
availableResolutionsAdaptive: resolutions?.adaptiveFormats,
metadata: {
description: microformat.description?.runs[0].text,
descriptionShort: details.shortDescription,
thumbnails:
details.thumbnails?.thumbnails || microformat.thumbnails?.thumbnails,
isFamilySafe: microformat.isFamilySafe,
availableCountries: microformat.availableCountries,
liveBroadcastDetails: microformat.liveBroadcastDetails,
uploadDate: microformat.uploadDate,
publishDate: microformat.publishDate,
isPrivate: details.isPrivate,
viewCount: details.viewCount || microformat.viewCount,
lengthSeconds: details.lengthSeconds || microformat.lengthSeconds,
likes: parseInt(
columnUI?.contents[1].slimVideoMetadataSectionRenderer?.contents[1].slimVideoActionBarRenderer?.buttons[0].slimMetadataToggleButtonRenderer?.button?.toggleButtonRenderer?.defaultText?.accessibility?.accessibilityData?.label?.replace(
/\D/g,
""
)
), // Yes. I know.
},
renderedData: {
description:
renderedPanels[0].engagementPanelSectionListRenderer?.content
.structuredDescriptionContentRenderer?.items[1]
.expandableVideoDescriptionBodyRenderer?.descriptionBodyText.runs,
recommendations:
columnUI?.contents[columnUI.contents.length - 1].itemSectionRenderer
?.contents,
},
};
}
} }
export default Innertube export default Innertube;

View file

@ -1,63 +1,89 @@
import Innertube from "./innertube"; import Innertube from "./innertube";
import constants from "./constants"; import constants from "./constants";
// Pointer object, give a key and it will return with a method // Pointer object, give a key and it will return with a method
function useRender (video, renderer) { function useRender(video, renderer) {
switch(renderer) { switch (renderer) {
case "videoWithContextRenderer": case "videoWithContextRenderer":
return videoWithContextRenderer(video) return videoWithContextRenderer(video);
case "gridVideoRenderer": case "gridVideoRenderer":
return gridVideoRenderer(video) return gridVideoRenderer(video);
case "compactAutoplayRenderer": case "compactAutoplayRenderer":
return compactAutoplayRenderer(video) return compactAutoplayRenderer(video);
default: default:
return undefined return undefined;
} }
} }
function gridVideoRenderer(video) { function gridVideoRenderer(video) {
return { return {
id: video.videoId, id: video.videoId,
title: video.title?.runs[0].text, title: video.title?.runs[0].text,
thumbnail: Innertube.getThumbnail(video.videoId, "max"), thumbnail: Innertube.getThumbnail(video.videoId, "max"),
channel: video.shortBylineText?.runs[0] ? video.shortBylineText.runs[0].text : video.longBylineText?.runs[0].text, channel: video.shortBylineText?.runs[0]
channelId: (video.shortBylineText?.runs[0] ? video.shortBylineText.runs[0] : video.longBylineText?.runs[0]).navigationEndpoint?.browseEndpoint?.browseId, ? video.shortBylineText.runs[0].text
channelURL: `${constants.YT_URL}/${(video.shortBylineText?.runs[0] ? video.shortBylineText.runs[0] : video.longBylineText?.runs[0]).navigationEndpoint?.browseEndpoint?.canonicalBaseUrl}`, : video.longBylineText?.runs[0].text,
channelThumbnail: video.channelThumbnail?.thumbnails[0], channelId: (video.shortBylineText?.runs[0]
metadata: { ? video.shortBylineText.runs[0]
published: video.publishedTimeText?.runs[0].text, : video.longBylineText?.runs[0]
views: video.shortViewCountText?.runs[0].text, ).navigationEndpoint?.browseEndpoint?.browseId,
length: video.lengthText?.runs[0].text, channelURL: `${constants.YT_URL}/${
overlayStyle: video.thumbnailOverlays?.map(overlay => overlay.thumbnailOverlayTimeStatusRenderer?.style), (video.shortBylineText?.runs[0]
overlay: video.thumbnailOverlays?.map(overlay => overlay.thumbnailOverlayTimeStatusRenderer?.text.runs[0].text), ? video.shortBylineText.runs[0]
}, : video.longBylineText?.runs[0]
}; ).navigationEndpoint?.browseEndpoint?.canonicalBaseUrl
}`,
channelThumbnail: video.channelThumbnail?.thumbnails[0],
metadata: {
published: video.publishedTimeText?.runs[0].text,
views: video.shortViewCountText?.runs[0].text,
length: video.lengthText?.runs[0].text,
overlayStyle: video.thumbnailOverlays?.map(
(overlay) => overlay.thumbnailOverlayTimeStatusRenderer?.style
),
overlay: video.thumbnailOverlays?.map(
(overlay) =>
overlay.thumbnailOverlayTimeStatusRenderer?.text.runs[0].text
),
},
};
} }
function videoWithContextRenderer(video) { function videoWithContextRenderer(video) {
return { return {
id: video.videoId, id: video.videoId,
title: video.headline?.runs[0].text, title: video.headline?.runs[0].text,
thumbnail: Innertube.getThumbnail(video.videoId, "max"), thumbnail: Innertube.getThumbnail(video.videoId, "max"),
channel: video.shortBylineText?.runs[0].text, channel: video.shortBylineText?.runs[0].text,
channelURL: video.channelThumbnail?.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl, channelURL:
channelId: video.channelThumbnail?.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.browseId, video.channelThumbnail?.channelThumbnailWithLinkRenderer
channelThumbnail: video.channelThumbnail?.channelThumbnailWithLinkRenderer?.thumbnail.thumbnails[0].url, ?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl,
metadata: { channelId:
views: video.shortViewCountText?.runs[0].text, video.channelThumbnail?.channelThumbnailWithLinkRenderer
length: video.lengthText?.runs[0].text, ?.navigationEndpoint?.browseEndpoint?.browseId,
overlayStyle: video.thumbnailOverlays?.map(overlay => overlay.thumbnailOverlayTimeStatusRenderer?.style), channelThumbnail:
overlay: video.thumbnailOverlays?.map(overlay => overlay.thumbnailOverlayTimeStatusRenderer?.text.runs[0].text), video.channelThumbnail?.channelThumbnailWithLinkRenderer?.thumbnail
isWatched: video.isWatched, .thumbnails[0].url,
}, metadata: {
} views: video.shortViewCountText?.runs[0].text,
length: video.lengthText?.runs[0].text,
overlayStyle: video.thumbnailOverlays?.map(
(overlay) => overlay.thumbnailOverlayTimeStatusRenderer?.style
),
overlay: video.thumbnailOverlays?.map(
(overlay) =>
overlay.thumbnailOverlayTimeStatusRenderer?.text.runs[0].text
),
isWatched: video.isWatched,
},
};
} }
function compactAutoplayRenderer(video) { function compactAutoplayRenderer(video) {
video = video.contents video = video.contents;
let item; let item;
if (video) item = video[0] if (video) item = video[0];
if (item) return useRender(item[Object.keys(item)[0]], Object.keys(item)[0]) if (item) return useRender(item[Object.keys(item)[0]], Object.keys(item)[0]);
else return undefined else return undefined;
} }
export default useRender export default useRender;

View file

@ -1,103 +1,99 @@
//--- Modules/Imports ---// //--- Modules/Imports ---//
import { Http } from '@capacitor-community/http'; import { Http } from "@capacitor-community/http";
import constants from './constants'; import constants from "./constants";
function generateUserID(length = 36) { function generateUserID(length = 36) {
const charset = const charset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = ""; let result = "";
if (crypto && crypto.getRandomValues) { if (crypto && crypto.getRandomValues) {
const values = new Uint32Array(length); const values = new Uint32Array(length);
crypto.getRandomValues(values); crypto.getRandomValues(values);
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
result += charset[values[i] % charset.length]; result += charset[values[i] % charset.length];
}
return result;
} else {
for (let i = 0; i < length; i++) {
result += charset[Math.floor(Math.random() * charset.length)];
}
return result;
} }
return result;
} else {
for (let i = 0; i < length; i++) {
result += charset[Math.floor(Math.random() * charset.length)];
}
return result;
} }
}
function countLeadingZeroes(uInt8View, limit) { function countLeadingZeroes(uInt8View, limit) {
let zeroes = 0; let zeroes = 0;
let value = 0; let value = 0;
for (let i = 0; i < uInt8View.length; i++) { for (let i = 0; i < uInt8View.length; i++) {
value = uInt8View[i]; value = uInt8View[i];
if (value === 0) { if (value === 0) {
zeroes += 8; zeroes += 8;
} else { } else {
let count = 1; let count = 1;
if (value >>> 4 === 0) { if (value >>> 4 === 0) {
count += 4; count += 4;
value <<= 4; value <<= 4;
}
if (value >>> 6 === 0) {
count += 2;
value <<= 2;
}
zeroes += count - (value >>> 7);
break;
} }
if (zeroes >= limit) { if (value >>> 6 === 0) {
break; count += 2;
value <<= 2;
} }
zeroes += count - (value >>> 7);
break;
}
if (zeroes >= limit) {
break;
} }
return zeroes;
} }
return zeroes;
}
//--- Puzzle Solver from Anarios --// //--- Puzzle Solver from Anarios --//
async function solvePuzzle(puzzle) { async function solvePuzzle(puzzle) {
let challenge = Uint8Array.from(atob(puzzle.challenge), (c) => let challenge = Uint8Array.from(atob(puzzle.challenge), (c) =>
c.charCodeAt(0) c.charCodeAt(0)
); );
let buffer = new ArrayBuffer(20); let buffer = new ArrayBuffer(20);
let uInt8View = new Uint8Array(buffer); let uInt8View = new Uint8Array(buffer);
let uInt32View = new Uint32Array(buffer); let uInt32View = new Uint32Array(buffer);
let maxCount = Math.pow(2, puzzle.difficulty) * 5; let maxCount = Math.pow(2, puzzle.difficulty) * 5;
for (let i = 4; i < 20; i++) { for (let i = 4; i < 20; i++) {
uInt8View[i] = challenge[i - 4]; uInt8View[i] = challenge[i - 4];
} }
for (let i = 0; i < maxCount; i++) { for (let i = 0; i < maxCount; i++) {
uInt32View[0] = i; uInt32View[0] = i;
let hash = await crypto.subtle.digest("SHA-512", buffer); let hash = await crypto.subtle.digest("SHA-512", buffer);
let hashUint8 = new Uint8Array(hash); let hashUint8 = new Uint8Array(hash);
if (countLeadingZeroes(hashUint8) >= puzzle.difficulty) { if (countLeadingZeroes(hashUint8) >= puzzle.difficulty) {
return { return {
solution: btoa(String.fromCharCode.apply(null, uInt8View.slice(0, 4))), solution: btoa(String.fromCharCode.apply(null, uInt8View.slice(0, 4))),
}; };
}
} }
} }
}
const rydModule = { const rydModule = {
logs: new Array(), logs: new Array(),
//--- Get Dislikes ---// //--- Get Dislikes ---//
getDislikes(id, callback) { getDislikes(id, callback) {
console.log("fetching ryd") console.log("fetching ryd");
Http.request({ Http.request({
method: 'GET', method: "GET",
url: `https://returnyoutubedislikeapi.com/votes`, url: `https://returnyoutubedislikeapi.com/votes`,
params: { videoId: id } params: { videoId: id },
}) })
.then((res) => { .then((res) => {
callback(res.data) callback(res.data);
}) })
.catch((err) => { .catch((err) => {
callback(err); callback(err);
}); });
} },
};
}
//--- Start ---// //--- Start ---//
export default ({ app }, inject) => { export default ({ app }, inject) => {
inject('ryd', {...rydModule}) inject("ryd", { ...rydModule });
} };

View file

@ -1,7 +1,10 @@
// Collection of functions that are useful but non-specific to any particular files // Collection of functions that are useful but non-specific to any particular files
function getBetweenStrings(data, start_string, end_string) { function getBetweenStrings(data, start_string, end_string) {
const regex = new RegExp(`${escapeRegExp(start_string)}(.*?)${escapeRegExp(end_string)}`, "s"); const regex = new RegExp(
`${escapeRegExp(start_string)}(.*?)${escapeRegExp(end_string)}`,
"s"
);
const match = data.match(regex); const match = data.match(regex);
return match ? match[1] : undefined; return match ? match[1] : undefined;
} }
@ -10,15 +13,15 @@ function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
} }
function hexToRgb(hex) { function hexToRgb(hex) {
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? { return result
r: parseInt(result[1], 16), ? {
g: parseInt(result[2], 16), r: parseInt(result[1], 16),
b: parseInt(result[3], 16) g: parseInt(result[2], 16),
} : null; b: parseInt(result[3], 16),
}
: null;
} }
function rgbToHex(r, g, b) { function rgbToHex(r, g, b) {
@ -28,5 +31,5 @@ function rgbToHex(r, g, b) {
module.exports = { module.exports = {
getBetweenStrings, getBetweenStrings,
hexToRgb, hexToRgb,
rgbToHex rgbToHex,
}; };

View file

@ -1,73 +1,71 @@
//--- Modules/Imports ---// //--- Modules/Imports ---//
import { Http } from '@capacitor-community/http'; import { Http } from "@capacitor-community/http";
import { StatusBar, Style } from '@capacitor/status-bar'; import { StatusBar, Style } from "@capacitor/status-bar";
import constants from './constants'; import constants from "./constants";
import { hexToRgb, rgbToHex } from './utils'; import { hexToRgb, rgbToHex } from "./utils";
const module = { const module = {
//--- Get GitHub Commits ---//
commits: new Promise((resolve, reject) => {
Http.request({
method: "GET",
url: `${constants.URLS.VT_GITHUB}/commits`,
params: {},
})
.then((res) => {
resolve(res.data);
})
.catch((err) => {
reject(err);
});
}),
//--- Get GitHub Commits ---// getRuns(item, callback) {
commits: new Promise((resolve, reject) => { let url = `${constants.URLS.VT_GITHUB}/commits/${item.sha}/check-runs`;
Http.request({ Http.request({
method: 'GET', method: "GET",
url: `${constants.URLS.VT_GITHUB}/commits`, url: url,
params: {} params: {},
}) })
.then((res) => { .then((res) => {
resolve(res.data) callback(res.data);
}) })
.catch((err) => { .catch((err) => {
reject(err) callback(err);
}); });
},
}),
getRuns(item, callback) {
let url = `${constants.URLS.VT_GITHUB}/commits/${item.sha}/check-runs`;
Http.request({
method: 'GET',
url: url,
params: {}
})
.then((res) => {
callback(res.data)
})
.catch((err) => {
callback(err)
});
statusBar: {
async hide() {
return await StatusBar.hide();
}, },
async show() {
statusBar: { return await StatusBar.show();
async hide() {
return await StatusBar.hide();
},
async show() {
return await StatusBar.show();
},
async setLight() {
return await StatusBar.setStyle({ style: Style.Light });
},
async setDark() {
return await StatusBar.setStyle({ style: Style.Dark });
},
async setTransparent() {
return StatusBar.setOverlaysWebView({ overlay: true });
},
async setBackground(color) {
return await StatusBar.setBackgroundColor({color: color});
}
}, },
async setLight() {
return await StatusBar.setStyle({ style: Style.Light });
},
async setDark() {
return await StatusBar.setStyle({ style: Style.Dark });
},
async setTransparent() {
return StatusBar.setOverlaysWebView({ overlay: true });
},
async setBackground(color) {
return await StatusBar.setBackgroundColor({ color: color });
},
},
hexToRgb(hex) { return hexToRgb(hex); }, hexToRgb(hex) {
rgbToHex(r, g, b) { return rgbToHex(r, g, b); } return hexToRgb(hex);
},
} rgbToHex(r, g, b) {
return rgbToHex(r, g, b);
},
};
//--- Start ---// //--- Start ---//
export default ({ app }, inject) => { export default ({ app }, inject) => {
inject('vuetube', module) inject("vuetube", module);
} };

View file

@ -1,172 +1,192 @@
//--- Modules/Imports ---// //--- Modules/Imports ---//
import { Http } from '@capacitor-community/http'; import { Http } from "@capacitor-community/http";
import Innertube from './innertube' import Innertube from "./innertube";
import constants from './constants'; import constants from "./constants";
import useRender from './renderers'; import useRender from "./renderers";
//--- Logger Function ---// //--- Logger Function ---//
function logger(func, data, isError = false) { function logger(func, data, isError = false) {
searchModule.logs.unshift({ searchModule.logs.unshift({
name: func, name: func,
time: Date.now(), time: Date.now(),
data: data, data: data,
error: isError error: isError,
}) });
} }
//--- Youtube Base Parser ---// //--- Youtube Base Parser ---//
function youtubeParse(html, callback) { function youtubeParse(html, callback) {
//--- Replace Encoded Characters ---/// //--- Replace Encoded Characters ---///
html = html.replace(/\\x([0-9A-F]{2})/ig, (...items) => { return String.fromCharCode(parseInt(items[1], 16)); }); html = html.replace(/\\x([0-9A-F]{2})/gi, (...items) => {
//--- Properly Format JSON ---// return String.fromCharCode(parseInt(items[1], 16));
html = html.replaceAll("\\\\\"", ""); });
//--- Parse JSON ---// //--- Properly Format JSON ---//
html = JSON.parse(html); 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 //--- Get Results ---// ( Thanks To appit-online On Github ) -> https://github.com/appit-online/youtube-search/blob/master/src/lib/search.ts
let results; let results;
if (html && html.contents && html.contents.sectionListRenderer && html.contents.sectionListRenderer.contents && if (
html.contents.sectionListRenderer.contents.length > 0 && html &&
html.contents.sectionListRenderer.contents[0].itemSectionRenderer && html.contents &&
html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents.length > 0) { html.contents.sectionListRenderer &&
results = html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents; html.contents.sectionListRenderer.contents &&
logger(constants.LOGGER_NAMES.search, results); html.contents.sectionListRenderer.contents.length > 0 &&
callback(results); html.contents.sectionListRenderer.contents[0].itemSectionRenderer &&
} else { html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents
try { .length > 0
results = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]); ) {
logger(constants.LOGGER_NAMES.search, results); results =
callback(results); html.contents.sectionListRenderer.contents[0].itemSectionRenderer
} catch (e) {} .contents;
try { logger(constants.LOGGER_NAMES.search, results);
results = JSON.parse(html.split('{"itemSectionRenderer":')[html.split('{"itemSectionRenderer":').length - 1].split('},{"continuationItemRenderer":{')[0]).contents; callback(results);
logger(constants.LOGGER_NAMES.search, results); } else {
callback(results); try {
} catch (e) {} 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 ---// //--- Search Main Function ---//
function youtubeSearch(text, callback) { function youtubeSearch(text, callback) {
Http.request({ Http.request({
method: 'GET', method: "GET",
url: `${constants.URLS.YT_URL}/results`, url: `${constants.URLS.YT_URL}/results`,
params: { q: text, hl: "en" } params: { q: text, hl: "en" },
}) })
.then((res) => { .then((res) => {
//--- Get HTML Only ---// //--- Get HTML Only ---//
let html = res.data; let html = res.data;
//--- Isolate The Script Containing Video Information ---// //--- Isolate The Script Containing Video Information ---//
html = html.split("var ytInitialData = '")[1].split("';</script>")[0]; html = html.split("var ytInitialData = '")[1].split("';</script>")[0];
youtubeParse(html, (data) => { youtubeParse(html, (data) => {
callback(data); callback(data);
}) });
})
.catch((err) => {
logger(constants.LOGGER_NAMES.search, err, true);
}) callback(err);
.catch((err) => { });
logger(constants.LOGGER_NAMES.search, err, true);
callback(err);
});
} }
const searchModule = { const searchModule = {
logs: new Array(), logs: new Array(),
//--- Get YouTube's Search Auto Complete ---// //--- Get YouTube's Search Auto Complete ---//
autoComplete(text, callback) { autoComplete(text, callback) {
Http.request({ Http.request({
method: 'GET', method: "GET",
url: `${constants.URLS.YT_SUGGESTIONS}/search`, url: `${constants.URLS.YT_SUGGESTIONS}/search`,
params: { client: 'youtube', q: text } params: { client: "youtube", q: text },
}) })
.then((res) => { .then((res) => {
logger(constants.LOGGER_NAMES.autoComplete, res); logger(constants.LOGGER_NAMES.autoComplete, res);
callback(res.data); callback(res.data);
}) })
.catch((err) => { .catch((err) => {
logger(constants.LOGGER_NAMES.autoComplete, err, true); logger(constants.LOGGER_NAMES.autoComplete, err, true);
callback(err); callback(err);
}); });
}, },
search(text, callback) { search(text, callback) {
let results = new Array();
youtubeSearch(text, (videos) => {
for (const i in videos) {
const video = videos[i];
let results = new Array(); if (video.compactVideoRenderer) {
youtubeSearch(text, (videos) => { //--- If Entry Is A Video ---//
for (const i in videos) { results.push({
const video = videos[i]; 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);
},
if (video.compactVideoRenderer) { getRemainingVideoInfo(id, callback) {
//--- If Entry Is A Video ---// String.prototype.decodeEscapeSequence = function () {
results.push({ return this.replace(/\\x([0-9A-Fa-f]{2})/g, function () {
id: video.compactVideoRenderer.videoId, return String.fromCharCode(parseInt(arguments[1], 16));
title: video.compactVideoRenderer.title.runs[0].text, });
runtime: video.compactVideoRenderer.lengthText.runs[0].text, };
uploaded: video.compactVideoRenderer.publishedTimeText.runs[0].text, Http.request({
views: video.compactVideoRenderer.viewCountText.runs[0].text, method: "GET",
thumbnails: video.compactVideoRenderer.thumbnail.thumbnails url: `${constants.URLS.YT_URL}/watch`,
}) params: { v: id },
} else { })
//--- If Entry Is Not A Video ---// .then((res) => {
//logger(constants.LOGGER_NAMES.search, { type: "Error Caught Successfully", error: video }, true); let dataUpdated = res.data.decodeEscapeSequence();
} let likes = dataUpdated
.split(
`"defaultIcon":{"iconType":"LIKE"},"defaultText":{"runs":[{"text":"`
} )[1]
}) .split(`"}],"accessibility":`)[0];
callback(results); let uploadDate = dataUpdated
.split(`"uploadDate":"`)[1]
}, .split(`}},"trackingParams":"`)[0]
.slice(0, -2);
getRemainingVideoInfo(id, callback) { let data = {
String.prototype.decodeEscapeSequence = function() { likes: likes,
return this.replace(/\\x([0-9A-Fa-f]{2})/g, function() { uploadDate: uploadDate,
return String.fromCharCode(parseInt(arguments[1], 16));
});
}; };
Http.request({ logger("vidData", data);
method: 'GET', callback(data);
url: `${constants.URLS.YT_URL}/watch`, })
params: { v: id } .catch((err) => {
}) logger("codeRun", err, true);
.then((res) => { callback(err);
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({
getReturnYoutubeDislike(id, callback) { method: "GET",
Http.request({ url: `https://returnyoutubedislikeapi.com/votes`,
method: 'GET', params: { videoId: id },
url: `https://returnyoutubedislikeapi.com/votes`, })
params: { videoId: id } .then((res) => {
}) logger("rydData", res.data);
.then((res) => { callback(res.data);
logger("rydData", res.data) })
callback(res.data) .catch((err) => {
}) logger("codeRun", err, true);
.catch((err) => { callback(err);
logger("codeRun", err, true); });
callback(err); },
}); };
}
}
//--- Recommendations ---// //--- Recommendations ---//
@ -175,58 +195,71 @@ 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) // 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 // 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 = { const innertubeModule = {
async getAPI() {
if (!InnertubeAPI) {
InnertubeAPI = await Innertube.createAsync((message, isError) => {
logger(constants.LOGGER_NAMES.innertube, message, isError);
});
}
return InnertubeAPI;
},
async getAPI() { async getVid(id) {
if (!InnertubeAPI) { try {
InnertubeAPI = await Innertube.createAsync((message, isError) => { logger(constants.LOGGER_NAMES.innertube, message, isError); }) return await InnertubeAPI.VidInfoAsync(id);
} catch (error) {
logger(constants.LOGGER_NAMES.watch, error, true);
}
},
// 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.map((item) => {
if (item) {
const renderedItem = useRender(
item[Object.keys(item)[0]],
Object.keys(item)[0]
);
console.log(renderedItem);
return renderedItem;
} else {
return undefined;
}
});
});
console.log(final);
return final;
},
// This is the recommendations that exist under videos
viewRecommends(recommendList) {
if (recommendList)
return recommendList.map((item) => {
if (item) {
return useRender(item[Object.keys(item)[0]], Object.keys(item)[0]);
} else {
return undefined;
} }
return InnertubeAPI; });
}, },
};
async getVid(id) {
try {
return await InnertubeAPI.VidInfoAsync(id)
} catch (error) {
logger(constants.LOGGER_NAMES.watch, error, true)
}
},
// 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.map((item) => {
if (item) {
const renderedItem = useRender(item[Object.keys(item)[0]], Object.keys(item)[0])
console.log(renderedItem)
return renderedItem
} else {return undefined}
})
})
console.log(final)
return final
},
// This is the recommendations that exist under videos
viewRecommends(recommendList) {
if (recommendList) return recommendList.map((item) => {
if (item) {
return useRender(item[Object.keys(item)[0]], Object.keys(item)[0])
} else {return undefined}
})
},
}
//--- Start ---// //--- Start ---//
export default ({ app }, inject) => { export default ({ app }, inject) => {
inject('youtube', {...searchModule, ...innertubeModule }) inject("youtube", { ...searchModule, ...innertubeModule });
inject("logger", logger) inject("logger", logger);
} };
logger(constants.LOGGER_NAMES.init, "Program Started"); logger(constants.LOGGER_NAMES.init, "Program Started");

View file

@ -1 +1 @@
/* activate vuex store */ /* activate vuex store */

View file

@ -1,18 +1,18 @@
export const state = () => ({ export const state = () => ({
roundTweak: 0 roundTweak: 0,
}) });
export const mutations = { export const mutations = {
initTweaks(state) { initTweaks(state) {
// NOTE: localStorage is not reactive, so it will only be used on first load // NOTE: localStorage is not reactive, so it will only be used on first load
// currently called beforeCreate() in pages/default.vue // currently called beforeCreate() in pages/default.vue
if (process.client) { if (process.client) {
state.roundTweak = localStorage.getItem("roundTweak") || 0 state.roundTweak = localStorage.getItem("roundTweak") || 0;
}
},
setRoundTweak (state, payload) {
if (!isNaN(payload)) {
state.roundTweak = payload
localStorage.setItem("roundTweak", payload)
}
} }
} },
setRoundTweak(state, payload) {
if (!isNaN(payload)) {
state.roundTweak = payload;
localStorage.setItem("roundTweak", payload);
}
},
};