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:
parent
777a0da738
commit
ad9d916627
29 changed files with 1023 additions and 866 deletions
|
@ -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()"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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: "",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
|
@ -26,4 +26,4 @@ export default {
|
||||||
.catch((error) => this.$logger("Home Page", error, true));
|
.catch((error) => this.$logger("Home Page", error, true));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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' }"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 });
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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);
|
||||||
}
|
};
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
/* activate vuex store */
|
/* activate vuex store */
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue