mirror of
https://github.com/VueTubeApp/VueTube
synced 2024-11-29 06:33:05 +00:00
Merge pull request #156 from PickleNik/main
great work nik (for once) /s
This commit is contained in:
commit
15aef74075
34 changed files with 1087 additions and 872 deletions
10
NUXT/.eslintignore
Normal file
10
NUXT/.eslintignore
Normal file
|
@ -0,0 +1,10 @@
|
|||
# js vendor file with import/require
|
||||
assets/vendor/**
|
||||
# static vendor file . use with nuxt.config.js script
|
||||
static/**
|
||||
# dependencies
|
||||
node_modules
|
||||
# Nuxt build
|
||||
.nuxt
|
||||
# Nuxt generate
|
||||
dist
|
24
NUXT/.eslintrc.js
Normal file
24
NUXT/.eslintrc.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
browser: true,
|
||||
},
|
||||
parserOptions: {
|
||||
parser: "babel-eslint",
|
||||
},
|
||||
extends: [
|
||||
"prettier",
|
||||
"eslint:recommended",
|
||||
"plugin:vue/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
],
|
||||
plugins: ["vue"],
|
||||
rules: {
|
||||
"vue/multi-word-component-names": 0,
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
// 'prettier/prettier': ['error', { semi: false }],
|
||||
// semi: [2, 'never'],
|
||||
},
|
||||
};
|
|
@ -6,19 +6,34 @@
|
|||
# install dependencies
|
||||
$ npm install
|
||||
|
||||
# serve with hot reload at localhost:3000
|
||||
# serve with hot reload
|
||||
$ npm run dev
|
||||
|
||||
# build for production and launch server
|
||||
# generate /android and /ios builds
|
||||
$ npm run build
|
||||
$ npm run start
|
||||
|
||||
# generate static project
|
||||
$ npm run generate
|
||||
|
||||
# lint files to avoid errors and formatting issues (do this before PR)
|
||||
$ npm run lint
|
||||
```
|
||||
|
||||
For detailed explanation on how things work, check out the [documentation](https://nuxtjs.org).
|
||||
|
||||
## Recommended VSCode Setup for Auto-Formatting
|
||||
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) `ext install dbaeumer.vscode-eslint`
|
||||
- [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode) `ext install esbenp.prettier-vscode`
|
||||
- [Vetur](https://marketplace.visualstudio.com/items?itemName=octref.vetur)
|
||||
|
||||
>`Ctrl(Cmd)` + `Shift` + `P` > Open Settings (JSON)
|
||||
```json
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
"vetur.validation.template": false,
|
||||
```
|
||||
|
||||
## Special Directories
|
||||
|
||||
You can create the following extra directories, some of which have special behaviors. Only `pages` is required; you can delete them if you don't want to use their functionality.
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
<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-for="(item, i) in tabs"
|
||||
:key="i"
|
||||
v-ripple="false"
|
||||
class="navButton"
|
||||
:to="item.link"
|
||||
plain
|
||||
v-ripple="false"
|
||||
>
|
||||
<span v-text="item.name" />
|
||||
<v-icon
|
||||
v-text="item.icon"
|
||||
:color="tabSelection == i ? 'primary' : 'grey'"
|
||||
:class="tabSelection == i ? 'tab primaryAlt' : ''"
|
||||
v-text="item.icon"
|
||||
/>
|
||||
</v-btn>
|
||||
<!-- <v-btn text class="navButton mr-2 fill-height" color="white" @click="searchBtn()"
|
||||
|
|
|
@ -12,12 +12,12 @@
|
|||
<div style="position: relative">
|
||||
<v-img :src="video.thumbnail" />
|
||||
<div
|
||||
v-text="video.metadata.overlay[0]"
|
||||
class="videoRuntimeFloat"
|
||||
style="color: #fff"
|
||||
v-text="video.metadata.overlay[0]"
|
||||
/>
|
||||
</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)" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
|
|
@ -4,22 +4,27 @@
|
|||
color="accent2"
|
||||
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-btn>
|
||||
|
||||
<v-text-field
|
||||
v-if="search"
|
||||
v-model="text"
|
||||
solo
|
||||
dense
|
||||
flat
|
||||
label="Search"
|
||||
v-model="text"
|
||||
@input="$emit('text-changed', text)"
|
||||
class="searchBar"
|
||||
v-if="search"
|
||||
v-on:keyup.enter="$emit('search-btn', text)"
|
||||
@input="$emit('text-changed', text)"
|
||||
@keyup.enter="$emit('search-btn', text)"
|
||||
/>
|
||||
|
||||
<v-spacer v-if="!search" />
|
||||
|
@ -33,11 +38,11 @@
|
|||
><v-icon>mdi-magnify</v-icon></v-btn
|
||||
>
|
||||
<v-btn
|
||||
v-show="!search"
|
||||
icon
|
||||
tile
|
||||
class="ml-4 mr-2 my-auto fill-height"
|
||||
style="border-radius: 0.25rem !important"
|
||||
v-show="!search"
|
||||
to="/settings"
|
||||
><v-icon>mdi-dots-vertical</v-icon></v-btn
|
||||
>
|
||||
|
@ -46,7 +51,16 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
props: ["search", "page"],
|
||||
props: {
|
||||
search: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
page: {
|
||||
type: String,
|
||||
default: "Home",
|
||||
},
|
||||
},
|
||||
events: ["searchBtn", "textChanged", "closeSearch"],
|
||||
data: () => ({
|
||||
text: "",
|
||||
|
|
|
@ -1,37 +1,47 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-snackbar v-model="updateSnackbar" class="updateBar" :timeout="updateSnackbarTimeout">
|
||||
{{ updateSnackbarText }}
|
||||
<div>
|
||||
<v-snackbar
|
||||
v-model="updateSnackbar"
|
||||
class="updateBar"
|
||||
:timeout="updateSnackbarTimeout"
|
||||
>
|
||||
{{ updateSnackbarText }}
|
||||
|
||||
<template v-slot:action="{ attrs }">
|
||||
<v-btn color="primary" text v-bind="attrs" @click="updateSnackbar = false">Close</v-btn>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
<template #action="{ attrs }">
|
||||
<v-btn
|
||||
color="primary"
|
||||
text
|
||||
v-bind="attrs"
|
||||
@click="updateSnackbar = false"
|
||||
>Close</v-btn
|
||||
>
|
||||
</template>
|
||||
</v-snackbar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.updateBar {
|
||||
z-index: 99999999;
|
||||
z-index: 99999999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
updateSnackbar: false,
|
||||
updateSnackbarText: "An update is available",
|
||||
updateSnackbarTimeout: 5000
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
updateSnackbar: false,
|
||||
updateSnackbarText: "An update is available",
|
||||
updateSnackbarTimeout: 5000,
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
const commits = await this.$vuetube.commits;
|
||||
const appVersion = process.env.appVersion;
|
||||
if (appVersion !== commits[0].sha && appVersion !== 'dev-local') {
|
||||
this.updateSnackbar = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
async mounted() {
|
||||
const commits = await this.$vuetube.commits;
|
||||
const appVersion = process.env.appVersion;
|
||||
if (appVersion !== commits[0].sha && appVersion !== "dev-local") {
|
||||
this.updateSnackbar = true;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<v-app v-show="stateLoaded" style="background: black !important">
|
||||
<topNavigation
|
||||
:search="search"
|
||||
:page="page"
|
||||
@close-search="search = !search"
|
||||
@search-btn="searchBtn"
|
||||
@text-changed="textChanged"
|
||||
:search="search"
|
||||
:page="page"
|
||||
/>
|
||||
|
||||
<div class="accent2" style="height: 100%; margin-top: 4rem">
|
||||
|
@ -41,7 +41,7 @@
|
|||
"
|
||||
>
|
||||
<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-icon>mdi-magnify</v-icon>
|
||||
<v-btn
|
||||
|
@ -65,8 +65,8 @@
|
|||
|
||||
<style>
|
||||
* {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
|
||||
}
|
||||
.scroll-y {
|
||||
overflow-y: scroll !important; /* has to be scroll, not auto */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-app>
|
||||
<nuxt />
|
||||
<nuxt />
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,31 +1,28 @@
|
|||
<template>
|
||||
<v-app>
|
||||
<center>
|
||||
|
||||
<v-icon size="100">mdi-alert-circle</v-icon>
|
||||
<h1 class="grey--text">An error occured!</h1>
|
||||
<v-btn to="/">Reload Application</v-btn>
|
||||
<v-btn to="/logs">Show Logs</v-btn>
|
||||
|
||||
|
||||
<div style="margin-top: 5em; color: #999; font-size: 0.75em;">
|
||||
<div style="font-size: 1.4em;">Error Information</div>
|
||||
<div style="margin-top: 5em; color: #999; font-size: 0.75em">
|
||||
<div style="font-size: 1.4em">Error Information</div>
|
||||
<div>Code: {{ error.statusCode }}</div>
|
||||
<div>Path: {{ this.$route.fullPath }}</div>
|
||||
<div>Path: {{ $route.fullPath }}</div>
|
||||
</div>
|
||||
|
||||
</center>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
layout: 'empty',
|
||||
layout: "empty",
|
||||
props: {
|
||||
error: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
</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 ****
|
||||
* Data Storage:
|
||||
* localStorage.setItem("key", data)
|
||||
* localStorage.getItem('key')
|
||||
*/
|
||||
|
||||
* Data Storage:
|
||||
* localStorage.setItem("key", data)
|
||||
* localStorage.getItem('key')
|
||||
*/
|
||||
|
||||
export default {
|
||||
|
||||
//--- Bettertube Stuff ---//
|
||||
env: {
|
||||
appVersion: "dev-local",
|
||||
},
|
||||
|
||||
target: 'static',
|
||||
target: "static",
|
||||
plugins: [
|
||||
{ src: "~/plugins/youtube", mode: "client" },
|
||||
{ src: "~/plugins/vuetube", mode: "client" },
|
||||
{ src: "~/plugins/ryd", mode: "client"}
|
||||
{ src: "~/plugins/ryd", mode: "client" },
|
||||
],
|
||||
generate: {
|
||||
dir: '../dist'
|
||||
dir: "../dist",
|
||||
},
|
||||
|
||||
//--- Bettertube Debugging ---//
|
||||
server: {
|
||||
port: 80, // default: 3000 (Note: Running on ports below 1024 requires root privileges!)
|
||||
host: '0.0.0.0', // default: localhost,
|
||||
timing: false
|
||||
host: "0.0.0.0", // default: localhost,
|
||||
timing: false,
|
||||
},
|
||||
|
||||
//--- Default NUXT Stuff ---//
|
||||
head: {
|
||||
title: 'VueTube',
|
||||
title: "VueTube",
|
||||
htmlAttrs: {
|
||||
lang: 'en'
|
||||
lang: "en",
|
||||
},
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ name: 'format-detection', content: 'telephone=no' }
|
||||
]
|
||||
{ charset: "utf-8" },
|
||||
{ name: "viewport", content: "width=device-width, initial-scale=1" },
|
||||
{ name: "format-detection", content: "telephone=no" },
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
css: [],
|
||||
components: true,
|
||||
|
||||
buildModules: [
|
||||
'@nuxtjs/vuetify',
|
||||
],
|
||||
buildModules: ["@nuxtjs/vuetify"],
|
||||
modules: [],
|
||||
|
||||
vuetify: {
|
||||
customVariables: ['~/assets/variables.scss'],
|
||||
customVariables: ["~/assets/variables.scss"],
|
||||
theme: {
|
||||
dark: false,
|
||||
options: { customProperties: true },
|
||||
|
@ -64,7 +59,7 @@ export default {
|
|||
accent: "#CD201F",
|
||||
accent2: "#fff",
|
||||
background: "#fff",
|
||||
info: "#000"
|
||||
info: "#000",
|
||||
},
|
||||
dark: {
|
||||
primary: colors.red.darken2, //colors.blue.darken2
|
||||
|
@ -73,8 +68,8 @@ export default {
|
|||
accent2: "#222",
|
||||
background: "#333",
|
||||
info: "#fff",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
"dev": "nuxt",
|
||||
"start": "nuxt generate",
|
||||
"build": "nuxt generate",
|
||||
"generate": "nuxt generate"
|
||||
"generate": "nuxt generate",
|
||||
"lint": "eslint --fix --ext .js,.vue --ignore-path .eslintignore ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/splash-screen": "^1.2.2",
|
||||
|
@ -20,6 +21,13 @@
|
|||
"webpack": "^4.46.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/vuetify": "^1.12.3"
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<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>
|
||||
<p class="grey--text">Please read the VueTube FAQ for more information.</p>
|
||||
</center>
|
||||
|
|
|
@ -1,22 +1,25 @@
|
|||
<template>
|
||||
<center style="padding: 1em;">
|
||||
|
||||
<center style="padding: 1em">
|
||||
<div class="row pa-4" style="flex-direction: column">
|
||||
<div>
|
||||
<v-img src="/icon.svg" width="100px"/>
|
||||
<v-img src="/icon.svg" width="100px" />
|
||||
</div>
|
||||
<v-spacer/>
|
||||
<v-spacer />
|
||||
<div>
|
||||
<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://discord.gg/7P8KJrdd5W')"><v-icon>mdi-discord</v-icon></v-btn>
|
||||
<v-btn @click="openExternal('https://github.com/Frontesque/VueTube')"
|
||||
><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>
|
||||
|
||||
<h3 style="margin-top: 2em;">App Information</h3>
|
||||
<h3 style="margin-top: 2em">App Information</h3>
|
||||
<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>OS: {{ deviceInfo.operatingSystem }} ({{ deviceInfo.osVersion }})</div>
|
||||
<div>Model: {{ deviceInfo.model }}</div>
|
||||
|
@ -32,25 +35,25 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { Browser } from '@capacitor/browser';
|
||||
import { Device } from '@capacitor/device';
|
||||
import { Browser } from "@capacitor/browser";
|
||||
import { Device } from "@capacitor/device";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
version: process.env.appVersion,
|
||||
deviceInfo: "",
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
const info = await Device.getInfo();
|
||||
this.deviceInfo = info;
|
||||
},
|
||||
methods: {
|
||||
async openExternal(url) {
|
||||
await Browser.open({ url: url });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
const info = await Device.getInfo();
|
||||
this.deviceInfo = info
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
<template>
|
||||
<div>
|
||||
|
||||
<v-list-item v-for="(item, index) in logs" :key="index">
|
||||
<v-card class="card">
|
||||
<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 }}
|
||||
<span v-text="`• ${new Date(item.time).toLocaleString()}`" class="date" />
|
||||
<span
|
||||
class="date"
|
||||
v-text="`• ${new Date(item.time).toLocaleString()}`"
|
||||
/>
|
||||
</v-card-title>
|
||||
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel>
|
||||
<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-panels>
|
||||
|
||||
</v-card>
|
||||
</v-list-item>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -46,12 +48,12 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
logs: new Array()
|
||||
}
|
||||
logs: new Array(),
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const logs = this.$youtube.logs
|
||||
const logs = this.$youtube.logs;
|
||||
this.logs = logs;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,50 +1,38 @@
|
|||
<template>
|
||||
<div class="mainContainer pt-1">
|
||||
|
||||
<v-card class="pb-5">
|
||||
<v-card-title>Default Page</v-card-title>
|
||||
<v-card-text>
|
||||
|
||||
<v-select
|
||||
:items="pages"
|
||||
v-model="page"
|
||||
:items="pages"
|
||||
label="Default Page"
|
||||
solo
|
||||
></v-select>
|
||||
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
|
||||
page: "home",
|
||||
pages: [
|
||||
"home",
|
||||
"subscriptions",
|
||||
"library"
|
||||
]
|
||||
}
|
||||
pages: ["home", "subscriptions", "library"],
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
page: function (newVal) {
|
||||
localStorage.setItem("startPage", newVal);
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.page = localStorage.getItem("startPage") || "home";
|
||||
},
|
||||
|
||||
watch: {
|
||||
page: function (val, oldVal) {
|
||||
localStorage.setItem("startPage", val);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<template>
|
||||
<div class="py-1">
|
||||
|
||||
<v-card class="pb-5">
|
||||
<v-card-title>Global Base Color</v-card-title>
|
||||
<v-row class="ml-3 mr-6">
|
||||
|
@ -14,13 +13,25 @@
|
|||
@click="saveTheme($vuetify.theme.dark)"
|
||||
/>
|
||||
</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'
|
||||
? 'LCD'
|
||||
: 'OLED'
|
||||
$vuetify.theme.themes.dark.background === "#000" ? "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-row>
|
||||
</v-card>
|
||||
|
@ -28,64 +39,35 @@
|
|||
<v-card class="pb-5">
|
||||
<v-card-title>Accent Color</v-card-title>
|
||||
<v-card-text>
|
||||
|
||||
<v-alert color="primary" dense outlined type="warning">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-alert color="primary" dense outlined type="warning"
|
||||
>NOTE: This doesn't save after closing the app (yet)</v-alert
|
||||
>
|
||||
<v-color-picker
|
||||
v-model="accentColor"
|
||||
dot-size="5"
|
||||
hide-mode-switch
|
||||
mode="hexa"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
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);
|
||||
},
|
||||
|
||||
accentColor: "#ffffff",
|
||||
};
|
||||
},
|
||||
|
||||
watch: {
|
||||
accentColor: function (val, oldVal) {
|
||||
accentColor: function (val) {
|
||||
this.$vuetify.theme.currentTheme.primary = val;
|
||||
let primaryAlt = this.$vuetube.hexToRgb(val);
|
||||
|
||||
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) {
|
||||
primaryAlt[i] = primaryAlt[i] + rgbEdit; //Amount To Lighten By
|
||||
|
@ -93,15 +75,45 @@ export default {
|
|||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
}
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
thumb-size="64"
|
||||
></v-slider>
|
||||
<v-slider
|
||||
v-model="roundTweak"
|
||||
class="mr-2"
|
||||
label="Inner"
|
||||
v-model="roundTweak"
|
||||
:max="4"
|
||||
step="1"
|
||||
thumb-size="64"
|
||||
>
|
||||
<template v-slot:thumb-label="{ value }">
|
||||
<template #thumb-label="{ value }">
|
||||
<div
|
||||
class="pa-4 white text-red red-text red--text"
|
||||
:style="{ borderRadius: value * 3 + 'px !important' }"
|
||||
|
|
|
@ -1,30 +1,42 @@
|
|||
<template>
|
||||
<div class="py-2">
|
||||
|
||||
<v-list-item v-for="(item, index) in commits" :key="index" class="my-1">
|
||||
<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 }}
|
||||
<span v-text="`• ${item.sha.substring(0, 7)}`" class="subtitle" />
|
||||
<span class="subtitle" v-text="`• ${item.sha.substring(0, 7)}`" />
|
||||
<v-spacer />
|
||||
<v-chip outlined class="tags" color="orange" v-if="index == 0">Latest</v-chip>
|
||||
<v-chip outlined class="tags" color="green" v-if="item.sha == installedVersion">Installed</v-chip>
|
||||
<v-chip v-if="index == 0" outlined class="tags" color="orange"
|
||||
>Latest</v-chip
|
||||
>
|
||||
<v-chip
|
||||
v-if="item.sha == installedVersion"
|
||||
outlined
|
||||
class="tags"
|
||||
color="green"
|
||||
>Installed</v-chip
|
||||
>
|
||||
</v-card-title>
|
||||
|
||||
<div style="margin-left: 1em;">
|
||||
<div class="date" v-text="new Date(item.commit.committer.date).toLocaleString()" />
|
||||
<div style="margin-left: 1em">
|
||||
<div
|
||||
class="date"
|
||||
v-text="new Date(item.commit.committer.date).toLocaleString()"
|
||||
/>
|
||||
{{ item.commit.message }}
|
||||
</div>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn @click="openExternal(item)"><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-btn @click="openExternal(item)"
|
||||
><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>
|
||||
</v-list-item>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -51,21 +63,22 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { Browser } from '@capacitor/browser';
|
||||
import { Browser } from "@capacitor/browser";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
commits: new Array(),
|
||||
installedVersion: process.env.appVersion
|
||||
}
|
||||
installedVersion: process.env.appVersion,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
const commits = await this.$vuetube.commits;
|
||||
if (commits[0].sha) { //If Commit Valid
|
||||
if (commits[0].sha) {
|
||||
//If Commit Valid
|
||||
this.commits = commits;
|
||||
} else {
|
||||
console.log(commits)
|
||||
console.log(commits);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -75,10 +88,9 @@ export default {
|
|||
|
||||
install(item) {
|
||||
this.$vuetube.getRuns(item, (data) => {
|
||||
console.log(data)
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -10,9 +10,13 @@
|
|||
<v-card-text>
|
||||
<div style="position: relative">
|
||||
<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 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}`" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
<template>
|
||||
<div style="padding-top: 1em;">
|
||||
<div style="padding-top: 1em">
|
||||
<v-list-item v-for="(item, index) in settingsItems" :key="index">
|
||||
|
||||
<v-btn text class="entry text-left text-capitalize" :to="item.to" :disabled="item.disabled">
|
||||
<v-icon v-text="item.icon" size="30px" class="icon" />
|
||||
<v-btn
|
||||
text
|
||||
class="entry text-left text-capitalize"
|
||||
:to="item.to"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<v-icon size="30px" class="icon" v-text="item.icon" />
|
||||
{{ item.name }}
|
||||
</v-btn>
|
||||
|
||||
</v-list-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
@ -30,15 +33,28 @@ export default {
|
|||
settingsItems: [
|
||||
{ name: "General", icon: "mdi-cog", to: "", disabled: true },
|
||||
{ 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: "Plugins", icon: "mdi-puzzle", to: "", disabled: true},
|
||||
{ name: "Updates", icon: "mdi-cloud-download-outline", to: "/mods/updates" },
|
||||
{ name: "Plugins", icon: "mdi-puzzle", to: "", disabled: true },
|
||||
{
|
||||
name: "Updates",
|
||||
icon: "mdi-cloud-download-outline",
|
||||
to: "/mods/updates",
|
||||
},
|
||||
{ name: "Logs", icon: "mdi-text-box-outline", to: "/mods/logs" },
|
||||
{ name: "About", icon: "mdi-information-outline", to: "/mods/about" },
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<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>
|
||||
<p class="grey--text">Please read the VueTube FAQ for more information.</p>
|
||||
</center>
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
>
|
||||
<v-btn
|
||||
text
|
||||
@click="item.action"
|
||||
class="vertical-button"
|
||||
style="padding: 0; margin: 0"
|
||||
elevation="0"
|
||||
:disabled="item.disabled"
|
||||
@click="item.action"
|
||||
>
|
||||
<v-icon v-text="item.icon" />
|
||||
<div v-text="item.value || item.name" />
|
||||
|
@ -40,7 +40,7 @@
|
|||
<p>Channel Stuff</p>
|
||||
<hr />
|
||||
</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 }}
|
||||
</div>
|
||||
|
||||
|
@ -72,7 +72,6 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style>
|
||||
.vertical-button span.v-btn__content {
|
||||
flex-direction: column;
|
||||
|
@ -82,12 +81,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
dislike() {},
|
||||
share() {
|
||||
this.share = !this.share;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
interactions: [
|
||||
|
@ -151,5 +144,11 @@ export default {
|
|||
this.interactions[1].value = data.dislikes.toLocaleString();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
dislike() {},
|
||||
share() {
|
||||
this.share = !this.share;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
// To centralize certain values and URLs as for easier debugging and refactoring
|
||||
|
||||
const url = {
|
||||
YT_URL: 'https://www.youtube.com',
|
||||
YT_MOBILE: "https://m.youtube.com",
|
||||
YT_MUSIC_URL: 'https://music.youtube.com',
|
||||
YT_BASE_API: 'https://www.youtube.com/youtubei/v1',
|
||||
YT_SUGGESTIONS: "https://suggestqueries.google.com/complete",
|
||||
VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube",
|
||||
}
|
||||
YT_URL: "https://www.youtube.com",
|
||||
YT_MOBILE: "https://m.youtube.com",
|
||||
YT_MUSIC_URL: "https://music.youtube.com",
|
||||
YT_BASE_API: "https://www.youtube.com/youtubei/v1",
|
||||
YT_SUGGESTIONS: "https://suggestqueries.google.com/complete",
|
||||
VT_GITHUB: "https://api.github.com/repos/Frontesque/VueTube",
|
||||
};
|
||||
|
||||
const ytApiVal = {
|
||||
VERSION: "16.25",
|
||||
CLIENTNAME: "ANDROID",
|
||||
VERSION_WEB: "2.20220318.00.00",
|
||||
CLIENT_WEB: 2
|
||||
}
|
||||
VERSION: "16.25",
|
||||
CLIENTNAME: "ANDROID",
|
||||
VERSION_WEB: "2.20220318.00.00",
|
||||
CLIENT_WEB: 2,
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
URLS: url,
|
||||
YT_API_VALUES: ytApiVal,
|
||||
URLS: url,
|
||||
YT_API_VALUES: ytApiVal,
|
||||
|
||||
LOGGER_NAMES: {
|
||||
search: "Search",
|
||||
autoComplete: "AutoComplete",
|
||||
watch: "Watch",
|
||||
recommendations: "Recommendations",
|
||||
init: "Initialize",
|
||||
innertube: "Innertube"
|
||||
},
|
||||
LOGGER_NAMES: {
|
||||
search: "Search",
|
||||
autoComplete: "AutoComplete",
|
||||
watch: "Watch",
|
||||
recommendations: "Recommendations",
|
||||
init: "Initialize",
|
||||
innertube: "Innertube",
|
||||
},
|
||||
|
||||
INNERTUBE_HEADER: (info) => {
|
||||
let headers = {
|
||||
accept: '*/*',
|
||||
'user-agent': info.userAgent,
|
||||
'accept-language': `${info.hl}-${info.gl},${info.hl};q=0.9`,
|
||||
'content-type': 'application/json',
|
||||
'x-goog-authuser': 0,
|
||||
'x-goog-visitor-id': info.visitorData || "",
|
||||
'x-youtube-client-name': ytApiVal.CLIENTNAME,
|
||||
'x-youtube-client-version': ytApiVal.VERSION,
|
||||
'x-origin': info.originalUrl,
|
||||
'origin': info.originalUrl,
|
||||
};
|
||||
return headers
|
||||
},
|
||||
INNERTUBE_HEADER: (info) => {
|
||||
let headers = {
|
||||
accept: "*/*",
|
||||
"user-agent": info.userAgent,
|
||||
"accept-language": `${info.hl}-${info.gl},${info.hl};q=0.9`,
|
||||
"content-type": "application/json",
|
||||
"x-goog-authuser": 0,
|
||||
"x-goog-visitor-id": info.visitorData || "",
|
||||
"x-youtube-client-name": ytApiVal.CLIENTNAME,
|
||||
"x-youtube-client-version": ytApiVal.VERSION,
|
||||
"x-origin": info.originalUrl,
|
||||
origin: info.originalUrl,
|
||||
};
|
||||
return headers;
|
||||
},
|
||||
|
||||
INNERTUBE_CLIENT: (info) => {
|
||||
let client = {
|
||||
"gl": info.gl,
|
||||
"hl": info.hl,
|
||||
"deviceMake": info.deviceMake,
|
||||
"deviceModel": info.deviceModel,
|
||||
"userAgent": info.userAgent,
|
||||
"clientName": ytApiVal.CLIENTNAME,
|
||||
"clientVersion": ytApiVal.VERSION,
|
||||
"osName": info.osName,
|
||||
"osVersion": info.osVersion,
|
||||
"platform": "MOBILE",
|
||||
"originalUrl": info.originalUrl,
|
||||
"configInfo": info.configInfo,
|
||||
"remoteHost": info.remoteHost,
|
||||
"visitorData": info.visitorData,
|
||||
};
|
||||
return client
|
||||
},
|
||||
}
|
||||
INNERTUBE_CLIENT: (info) => {
|
||||
let client = {
|
||||
gl: info.gl,
|
||||
hl: info.hl,
|
||||
deviceMake: info.deviceMake,
|
||||
deviceModel: info.deviceModel,
|
||||
userAgent: info.userAgent,
|
||||
clientName: ytApiVal.CLIENTNAME,
|
||||
clientVersion: ytApiVal.VERSION,
|
||||
osName: info.osName,
|
||||
osVersion: info.osVersion,
|
||||
platform: "MOBILE",
|
||||
originalUrl: info.originalUrl,
|
||||
configInfo: info.configInfo,
|
||||
remoteHost: info.remoteHost,
|
||||
visitorData: info.visitorData,
|
||||
};
|
||||
return client;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,180 +1,217 @@
|
|||
// Code specific to working with the innertube API
|
||||
// https://www.youtube.com/youtubei/v1
|
||||
|
||||
import { Http } from '@capacitor-community/http';
|
||||
import { getBetweenStrings } from './utils';
|
||||
import constants from './constants';
|
||||
import { Http } from "@capacitor-community/http";
|
||||
import { getBetweenStrings } from "./utils";
|
||||
import constants from "./constants";
|
||||
|
||||
class Innertube {
|
||||
//--- Initiation ---//
|
||||
|
||||
//--- Initiation ---//
|
||||
constructor(ErrorCallback) {
|
||||
this.ErrorCallback = ErrorCallback || undefined;
|
||||
this.retry_count = 0;
|
||||
}
|
||||
|
||||
constructor(ErrorCallback) {
|
||||
this.ErrorCallback = ErrorCallback || undefined;
|
||||
this.retry_count = 0
|
||||
checkErrorCallback() {
|
||||
return typeof this.ErrorCallback === "function";
|
||||
}
|
||||
|
||||
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() {
|
||||
return typeof this.ErrorCallback === "function"
|
||||
}
|
||||
console.log(data);
|
||||
|
||||
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;
|
||||
const response = await Http.post({
|
||||
url: `${constants.URLS.YT_BASE_API}/browse?key=${this.key}`,
|
||||
data: data,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
}).catch((error) => error);
|
||||
|
||||
this.context.client = constants.INNERTUBE_CLIENT(this.context.client)
|
||||
this.header = constants.INNERTUBE_HEADER(this.context.client)
|
||||
}
|
||||
if (response instanceof Error)
|
||||
return {
|
||||
success: false,
|
||||
status_code: response.status,
|
||||
message: response.message,
|
||||
};
|
||||
|
||||
} 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)
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
status_code: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
}
|
||||
|
||||
static async createAsync(ErrorCallback) {
|
||||
const created = new Innertube(ErrorCallback);
|
||||
await created.initAsync();
|
||||
return created;
|
||||
static getThumbnail(id, resolution) {
|
||||
if (resolution == "max") {
|
||||
const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg`;
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
if (img.height !== 120) return url;
|
||||
};
|
||||
}
|
||||
return `https://img.youtube.com/vi/${id}/mqdefault.jpg`;
|
||||
}
|
||||
|
||||
//--- 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) {
|
||||
let data = { context: this.context }
|
||||
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);
|
||||
|
||||
switch (action_type) {
|
||||
case 'recommendations':
|
||||
data.browseId = 'FEwhat_to_watch'
|
||||
break;
|
||||
case 'playlist':
|
||||
data.browseId = args.browse_id
|
||||
break;
|
||||
default:
|
||||
}
|
||||
if (response instanceof Error)
|
||||
return {
|
||||
success: false,
|
||||
status_code: response.response.status,
|
||||
message: response.message,
|
||||
};
|
||||
|
||||
console.log(data)
|
||||
return {
|
||||
success: true,
|
||||
status_code: response.status,
|
||||
data: { webOutput: response.data, appOutput: responseMobile.data },
|
||||
};
|
||||
}
|
||||
|
||||
const response = await Http.post({
|
||||
url: `${constants.URLS.YT_BASE_API}/browse?key=${this.key}`,
|
||||
data: data,
|
||||
headers: { "Content-Type": "application/json" }
|
||||
}).catch((error) => error);
|
||||
// Simple Wrappers
|
||||
async getRecommendationsAsync() {
|
||||
const rec = await this.browseAsync("recommendations");
|
||||
console.log(rec.data);
|
||||
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 {
|
||||
success: true,
|
||||
status_code: response.status,
|
||||
data: response.data
|
||||
};
|
||||
}
|
||||
|
||||
static getThumbnail(id, resolution) {
|
||||
if (resolution == "max"){
|
||||
const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg`
|
||||
let img = new Image();
|
||||
img.src = url
|
||||
img.onload = function(){
|
||||
if (img.height !== 120) return url
|
||||
};
|
||||
}
|
||||
return `https://img.youtube.com/vi/${id}/mqdefault.jpg`
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default Innertube
|
||||
export default Innertube;
|
||||
|
|
|
@ -1,63 +1,89 @@
|
|||
import Innertube from "./innertube";
|
||||
import Innertube from "./innertube";
|
||||
import constants from "./constants";
|
||||
|
||||
// Pointer object, give a key and it will return with a method
|
||||
function useRender (video, renderer) {
|
||||
switch(renderer) {
|
||||
case "videoWithContextRenderer":
|
||||
return videoWithContextRenderer(video)
|
||||
case "gridVideoRenderer":
|
||||
return gridVideoRenderer(video)
|
||||
case "compactAutoplayRenderer":
|
||||
return compactAutoplayRenderer(video)
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
function useRender(video, renderer) {
|
||||
switch (renderer) {
|
||||
case "videoWithContextRenderer":
|
||||
return videoWithContextRenderer(video);
|
||||
case "gridVideoRenderer":
|
||||
return gridVideoRenderer(video);
|
||||
case "compactAutoplayRenderer":
|
||||
return compactAutoplayRenderer(video);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function gridVideoRenderer(video) {
|
||||
return {
|
||||
id: video.videoId,
|
||||
title: video.title?.runs[0].text,
|
||||
thumbnail: Innertube.getThumbnail(video.videoId, "max"),
|
||||
channel: video.shortBylineText?.runs[0] ? video.shortBylineText.runs[0].text : video.longBylineText?.runs[0].text,
|
||||
channelId: (video.shortBylineText?.runs[0] ? video.shortBylineText.runs[0] : video.longBylineText?.runs[0]).navigationEndpoint?.browseEndpoint?.browseId,
|
||||
channelURL: `${constants.YT_URL}/${(video.shortBylineText?.runs[0] ? 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),
|
||||
},
|
||||
};
|
||||
return {
|
||||
id: video.videoId,
|
||||
title: video.title?.runs[0].text,
|
||||
thumbnail: Innertube.getThumbnail(video.videoId, "max"),
|
||||
channel: video.shortBylineText?.runs[0]
|
||||
? video.shortBylineText.runs[0].text
|
||||
: video.longBylineText?.runs[0].text,
|
||||
channelId: (video.shortBylineText?.runs[0]
|
||||
? video.shortBylineText.runs[0]
|
||||
: video.longBylineText?.runs[0]
|
||||
).navigationEndpoint?.browseEndpoint?.browseId,
|
||||
channelURL: `${constants.YT_URL}/${
|
||||
(video.shortBylineText?.runs[0]
|
||||
? 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) {
|
||||
return {
|
||||
id: video.videoId,
|
||||
title: video.headline?.runs[0].text,
|
||||
thumbnail: Innertube.getThumbnail(video.videoId, "max"),
|
||||
channel: video.shortBylineText?.runs[0].text,
|
||||
channelURL: video.channelThumbnail?.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl,
|
||||
channelId: video.channelThumbnail?.channelThumbnailWithLinkRenderer?.navigationEndpoint?.browseEndpoint?.browseId,
|
||||
channelThumbnail: video.channelThumbnail?.channelThumbnailWithLinkRenderer?.thumbnail.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,
|
||||
},
|
||||
}
|
||||
return {
|
||||
id: video.videoId,
|
||||
title: video.headline?.runs[0].text,
|
||||
thumbnail: Innertube.getThumbnail(video.videoId, "max"),
|
||||
channel: video.shortBylineText?.runs[0].text,
|
||||
channelURL:
|
||||
video.channelThumbnail?.channelThumbnailWithLinkRenderer
|
||||
?.navigationEndpoint?.browseEndpoint?.canonicalBaseUrl,
|
||||
channelId:
|
||||
video.channelThumbnail?.channelThumbnailWithLinkRenderer
|
||||
?.navigationEndpoint?.browseEndpoint?.browseId,
|
||||
channelThumbnail:
|
||||
video.channelThumbnail?.channelThumbnailWithLinkRenderer?.thumbnail
|
||||
.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) {
|
||||
video = video.contents
|
||||
let item;
|
||||
if (video) item = video[0]
|
||||
if (item) return useRender(item[Object.keys(item)[0]], Object.keys(item)[0])
|
||||
else return undefined
|
||||
video = video.contents;
|
||||
let item;
|
||||
if (video) item = video[0];
|
||||
if (item) return useRender(item[Object.keys(item)[0]], Object.keys(item)[0]);
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
export default useRender
|
||||
export default useRender;
|
||||
|
|
|
@ -1,103 +1,99 @@
|
|||
//--- Modules/Imports ---//
|
||||
import { Http } from '@capacitor-community/http';
|
||||
import constants from './constants';
|
||||
import { Http } from "@capacitor-community/http";
|
||||
import constants from "./constants";
|
||||
|
||||
function generateUserID(length = 36) {
|
||||
const charset =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
if (crypto && crypto.getRandomValues) {
|
||||
const values = new Uint32Array(length);
|
||||
crypto.getRandomValues(values);
|
||||
for (let i = 0; i < length; i++) {
|
||||
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;
|
||||
const charset =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let result = "";
|
||||
if (crypto && crypto.getRandomValues) {
|
||||
const values = new Uint32Array(length);
|
||||
crypto.getRandomValues(values);
|
||||
for (let i = 0; i < length; i++) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
function countLeadingZeroes(uInt8View, limit) {
|
||||
let zeroes = 0;
|
||||
let value = 0;
|
||||
for (let i = 0; i < uInt8View.length; i++) {
|
||||
value = uInt8View[i];
|
||||
if (value === 0) {
|
||||
zeroes += 8;
|
||||
} else {
|
||||
let count = 1;
|
||||
if (value >>> 4 === 0) {
|
||||
count += 4;
|
||||
value <<= 4;
|
||||
}
|
||||
if (value >>> 6 === 0) {
|
||||
count += 2;
|
||||
value <<= 2;
|
||||
}
|
||||
zeroes += count - (value >>> 7);
|
||||
break;
|
||||
let zeroes = 0;
|
||||
let value = 0;
|
||||
for (let i = 0; i < uInt8View.length; i++) {
|
||||
value = uInt8View[i];
|
||||
if (value === 0) {
|
||||
zeroes += 8;
|
||||
} else {
|
||||
let count = 1;
|
||||
if (value >>> 4 === 0) {
|
||||
count += 4;
|
||||
value <<= 4;
|
||||
}
|
||||
if (zeroes >= limit) {
|
||||
break;
|
||||
if (value >>> 6 === 0) {
|
||||
count += 2;
|
||||
value <<= 2;
|
||||
}
|
||||
zeroes += count - (value >>> 7);
|
||||
break;
|
||||
}
|
||||
if (zeroes >= limit) {
|
||||
break;
|
||||
}
|
||||
return zeroes;
|
||||
}
|
||||
return zeroes;
|
||||
}
|
||||
|
||||
//--- Puzzle Solver from Anarios --//
|
||||
async function solvePuzzle(puzzle) {
|
||||
let challenge = Uint8Array.from(atob(puzzle.challenge), (c) =>
|
||||
c.charCodeAt(0)
|
||||
);
|
||||
let buffer = new ArrayBuffer(20);
|
||||
let uInt8View = new Uint8Array(buffer);
|
||||
let uInt32View = new Uint32Array(buffer);
|
||||
let maxCount = Math.pow(2, puzzle.difficulty) * 5;
|
||||
for (let i = 4; i < 20; i++) {
|
||||
uInt8View[i] = challenge[i - 4];
|
||||
}
|
||||
|
||||
for (let i = 0; i < maxCount; i++) {
|
||||
uInt32View[0] = i;
|
||||
let hash = await crypto.subtle.digest("SHA-512", buffer);
|
||||
let hashUint8 = new Uint8Array(hash);
|
||||
if (countLeadingZeroes(hashUint8) >= puzzle.difficulty) {
|
||||
return {
|
||||
solution: btoa(String.fromCharCode.apply(null, uInt8View.slice(0, 4))),
|
||||
};
|
||||
}
|
||||
}
|
||||
let challenge = Uint8Array.from(atob(puzzle.challenge), (c) =>
|
||||
c.charCodeAt(0)
|
||||
);
|
||||
let buffer = new ArrayBuffer(20);
|
||||
let uInt8View = new Uint8Array(buffer);
|
||||
let uInt32View = new Uint32Array(buffer);
|
||||
let maxCount = Math.pow(2, puzzle.difficulty) * 5;
|
||||
for (let i = 4; i < 20; i++) {
|
||||
uInt8View[i] = challenge[i - 4];
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < maxCount; i++) {
|
||||
uInt32View[0] = i;
|
||||
let hash = await crypto.subtle.digest("SHA-512", buffer);
|
||||
let hashUint8 = new Uint8Array(hash);
|
||||
if (countLeadingZeroes(hashUint8) >= puzzle.difficulty) {
|
||||
return {
|
||||
solution: btoa(String.fromCharCode.apply(null, uInt8View.slice(0, 4))),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const rydModule = {
|
||||
logs: new Array(),
|
||||
logs: new Array(),
|
||||
|
||||
//--- Get Dislikes ---//
|
||||
getDislikes(id, callback) {
|
||||
console.log("fetching ryd")
|
||||
Http.request({
|
||||
method: 'GET',
|
||||
url: `https://returnyoutubedislikeapi.com/votes`,
|
||||
params: { videoId: id }
|
||||
})
|
||||
.then((res) => {
|
||||
callback(res.data)
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
//--- Get Dislikes ---//
|
||||
getDislikes(id, callback) {
|
||||
console.log("fetching ryd");
|
||||
Http.request({
|
||||
method: "GET",
|
||||
url: `https://returnyoutubedislikeapi.com/votes`,
|
||||
params: { videoId: id },
|
||||
})
|
||||
.then((res) => {
|
||||
callback(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
//--- Start ---//
|
||||
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
|
||||
|
||||
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);
|
||||
return match ? match[1] : undefined;
|
||||
}
|
||||
|
@ -10,15 +13,15 @@ function escapeRegExp(string) {
|
|||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
}
|
||||
|
||||
|
||||
|
||||
function hexToRgb(hex) {
|
||||
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result ? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16)
|
||||
} : null;
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
function rgbToHex(r, g, b) {
|
||||
|
@ -28,5 +31,5 @@ function rgbToHex(r, g, b) {
|
|||
module.exports = {
|
||||
getBetweenStrings,
|
||||
hexToRgb,
|
||||
rgbToHex
|
||||
rgbToHex,
|
||||
};
|
||||
|
|
|
@ -1,73 +1,71 @@
|
|||
//--- Modules/Imports ---//
|
||||
import { Http } from '@capacitor-community/http';
|
||||
import { StatusBar, Style } from '@capacitor/status-bar';
|
||||
import constants from './constants';
|
||||
import { hexToRgb, rgbToHex } from './utils';
|
||||
import { Http } from "@capacitor-community/http";
|
||||
import { StatusBar, Style } from "@capacitor/status-bar";
|
||||
import constants from "./constants";
|
||||
import { hexToRgb, rgbToHex } from "./utils";
|
||||
|
||||
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 ---//
|
||||
commits: new Promise((resolve, reject) => {
|
||||
getRuns(item, callback) {
|
||||
let url = `${constants.URLS.VT_GITHUB}/commits/${item.sha}/check-runs`;
|
||||
|
||||
Http.request({
|
||||
method: 'GET',
|
||||
url: `${constants.URLS.VT_GITHUB}/commits`,
|
||||
params: {}
|
||||
})
|
||||
.then((res) => {
|
||||
resolve(res.data)
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(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)
|
||||
});
|
||||
Http.request({
|
||||
method: "GET",
|
||||
url: url,
|
||||
params: {},
|
||||
})
|
||||
.then((res) => {
|
||||
callback(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
|
||||
statusBar: {
|
||||
async hide() {
|
||||
return await StatusBar.hide();
|
||||
},
|
||||
|
||||
statusBar: {
|
||||
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 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 });
|
||||
},
|
||||
},
|
||||
|
||||
hexToRgb(hex) { return hexToRgb(hex); },
|
||||
rgbToHex(r, g, b) { return rgbToHex(r, g, b); }
|
||||
|
||||
}
|
||||
hexToRgb(hex) {
|
||||
return hexToRgb(hex);
|
||||
},
|
||||
rgbToHex(r, g, b) {
|
||||
return rgbToHex(r, g, b);
|
||||
},
|
||||
};
|
||||
|
||||
//--- Start ---//
|
||||
export default ({ app }, inject) => {
|
||||
inject('vuetube', module)
|
||||
}
|
||||
inject("vuetube", module);
|
||||
};
|
||||
|
|
|
@ -1,172 +1,192 @@
|
|||
//--- Modules/Imports ---//
|
||||
import { Http } from '@capacitor-community/http';
|
||||
import Innertube from './innertube'
|
||||
import constants from './constants';
|
||||
import useRender from './renderers';
|
||||
import { Http } from "@capacitor-community/http";
|
||||
import Innertube from "./innertube";
|
||||
import constants from "./constants";
|
||||
import useRender from "./renderers";
|
||||
|
||||
//--- Logger Function ---//
|
||||
function logger(func, data, isError = false) {
|
||||
searchModule.logs.unshift({
|
||||
name: func,
|
||||
time: Date.now(),
|
||||
data: data,
|
||||
error: isError
|
||||
})
|
||||
searchModule.logs.unshift({
|
||||
name: func,
|
||||
time: Date.now(),
|
||||
data: data,
|
||||
error: isError,
|
||||
});
|
||||
}
|
||||
|
||||
//--- Youtube Base Parser ---//
|
||||
function youtubeParse(html, callback) {
|
||||
//--- Replace Encoded Characters ---///
|
||||
html = html.replace(/\\x([0-9A-F]{2})/ig, (...items) => { return String.fromCharCode(parseInt(items[1], 16)); });
|
||||
//--- Properly Format JSON ---//
|
||||
html = html.replaceAll("\\\\\"", "");
|
||||
//--- Parse JSON ---//
|
||||
html = JSON.parse(html);
|
||||
//--- Replace Encoded Characters ---///
|
||||
html = html.replace(/\\x([0-9A-F]{2})/gi, (...items) => {
|
||||
return String.fromCharCode(parseInt(items[1], 16));
|
||||
});
|
||||
//--- Properly Format JSON ---//
|
||||
html = html.replaceAll('\\\\"', "");
|
||||
//--- Parse JSON ---//
|
||||
html = JSON.parse(html);
|
||||
|
||||
//--- Get Results ---// ( Thanks To appit-online On Github ) -> https://github.com/appit-online/youtube-search/blob/master/src/lib/search.ts
|
||||
let results;
|
||||
if (html && html.contents && html.contents.sectionListRenderer && html.contents.sectionListRenderer.contents &&
|
||||
html.contents.sectionListRenderer.contents.length > 0 &&
|
||||
html.contents.sectionListRenderer.contents[0].itemSectionRenderer &&
|
||||
html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents.length > 0) {
|
||||
results = html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents;
|
||||
logger(constants.LOGGER_NAMES.search, results);
|
||||
callback(results);
|
||||
} else {
|
||||
try {
|
||||
results = JSON.parse(html.split('{"itemSectionRenderer":{"contents":')[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(',"continuations":[{')[0]);
|
||||
logger(constants.LOGGER_NAMES.search, results);
|
||||
callback(results);
|
||||
} catch (e) {}
|
||||
try {
|
||||
results = JSON.parse(html.split('{"itemSectionRenderer":')[html.split('{"itemSectionRenderer":').length - 1].split('},{"continuationItemRenderer":{')[0]).contents;
|
||||
logger(constants.LOGGER_NAMES.search, results);
|
||||
callback(results);
|
||||
} catch (e) {}
|
||||
}
|
||||
//--- Get Results ---// ( Thanks To appit-online On Github ) -> https://github.com/appit-online/youtube-search/blob/master/src/lib/search.ts
|
||||
let results;
|
||||
if (
|
||||
html &&
|
||||
html.contents &&
|
||||
html.contents.sectionListRenderer &&
|
||||
html.contents.sectionListRenderer.contents &&
|
||||
html.contents.sectionListRenderer.contents.length > 0 &&
|
||||
html.contents.sectionListRenderer.contents[0].itemSectionRenderer &&
|
||||
html.contents.sectionListRenderer.contents[0].itemSectionRenderer.contents
|
||||
.length > 0
|
||||
) {
|
||||
results =
|
||||
html.contents.sectionListRenderer.contents[0].itemSectionRenderer
|
||||
.contents;
|
||||
logger(constants.LOGGER_NAMES.search, results);
|
||||
callback(results);
|
||||
} else {
|
||||
try {
|
||||
results = JSON.parse(
|
||||
html
|
||||
.split('{"itemSectionRenderer":{"contents":')
|
||||
[html.split('{"itemSectionRenderer":{"contents":').length - 1].split(
|
||||
',"continuations":[{'
|
||||
)[0]
|
||||
);
|
||||
logger(constants.LOGGER_NAMES.search, results);
|
||||
callback(results);
|
||||
} catch (e) {}
|
||||
try {
|
||||
results = JSON.parse(
|
||||
html
|
||||
.split('{"itemSectionRenderer":')
|
||||
[html.split('{"itemSectionRenderer":').length - 1].split(
|
||||
'},{"continuationItemRenderer":{'
|
||||
)[0]
|
||||
).contents;
|
||||
logger(constants.LOGGER_NAMES.search, results);
|
||||
callback(results);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
//--- Search Main Function ---//
|
||||
function youtubeSearch(text, callback) {
|
||||
Http.request({
|
||||
method: 'GET',
|
||||
url: `${constants.URLS.YT_URL}/results`,
|
||||
params: { q: text, hl: "en" }
|
||||
})
|
||||
.then((res) => {
|
||||
//--- Get HTML Only ---//
|
||||
let html = res.data;
|
||||
//--- Isolate The Script Containing Video Information ---//
|
||||
html = html.split("var ytInitialData = '")[1].split("';</script>")[0];
|
||||
Http.request({
|
||||
method: "GET",
|
||||
url: `${constants.URLS.YT_URL}/results`,
|
||||
params: { q: text, hl: "en" },
|
||||
})
|
||||
.then((res) => {
|
||||
//--- Get HTML Only ---//
|
||||
let html = res.data;
|
||||
//--- Isolate The Script Containing Video Information ---//
|
||||
html = html.split("var ytInitialData = '")[1].split("';</script>")[0];
|
||||
|
||||
youtubeParse(html, (data) => {
|
||||
callback(data);
|
||||
})
|
||||
|
||||
|
||||
|
||||
})
|
||||
.catch((err) => {
|
||||
logger(constants.LOGGER_NAMES.search, err, true);
|
||||
callback(err);
|
||||
});
|
||||
youtubeParse(html, (data) => {
|
||||
callback(data);
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
logger(constants.LOGGER_NAMES.search, err, true);
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
const searchModule = {
|
||||
logs: new Array(),
|
||||
logs: new Array(),
|
||||
|
||||
//--- Get YouTube's Search Auto Complete ---//
|
||||
autoComplete(text, callback) {
|
||||
Http.request({
|
||||
method: 'GET',
|
||||
url: `${constants.URLS.YT_SUGGESTIONS}/search`,
|
||||
params: { client: 'youtube', q: text }
|
||||
})
|
||||
.then((res) => {
|
||||
logger(constants.LOGGER_NAMES.autoComplete, res);
|
||||
callback(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger(constants.LOGGER_NAMES.autoComplete, err, true);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
//--- Get YouTube's Search Auto Complete ---//
|
||||
autoComplete(text, callback) {
|
||||
Http.request({
|
||||
method: "GET",
|
||||
url: `${constants.URLS.YT_SUGGESTIONS}/search`,
|
||||
params: { client: "youtube", q: text },
|
||||
})
|
||||
.then((res) => {
|
||||
logger(constants.LOGGER_NAMES.autoComplete, res);
|
||||
callback(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger(constants.LOGGER_NAMES.autoComplete, err, true);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
|
||||
search(text, callback) {
|
||||
search(text, callback) {
|
||||
let results = new Array();
|
||||
youtubeSearch(text, (videos) => {
|
||||
for (const i in videos) {
|
||||
const video = videos[i];
|
||||
|
||||
let results = new Array();
|
||||
youtubeSearch(text, (videos) => {
|
||||
for (const i in videos) {
|
||||
const video = videos[i];
|
||||
if (video.compactVideoRenderer) {
|
||||
//--- If Entry Is A Video ---//
|
||||
results.push({
|
||||
id: video.compactVideoRenderer.videoId,
|
||||
title: video.compactVideoRenderer.title.runs[0].text,
|
||||
runtime: video.compactVideoRenderer.lengthText.runs[0].text,
|
||||
uploaded: video.compactVideoRenderer.publishedTimeText.runs[0].text,
|
||||
views: video.compactVideoRenderer.viewCountText.runs[0].text,
|
||||
thumbnails: video.compactVideoRenderer.thumbnail.thumbnails,
|
||||
});
|
||||
} else {
|
||||
//--- If Entry Is Not A Video ---//
|
||||
//logger(constants.LOGGER_NAMES.search, { type: "Error Caught Successfully", error: video }, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
callback(results);
|
||||
},
|
||||
|
||||
if (video.compactVideoRenderer) {
|
||||
//--- If Entry Is A Video ---//
|
||||
results.push({
|
||||
id: video.compactVideoRenderer.videoId,
|
||||
title: video.compactVideoRenderer.title.runs[0].text,
|
||||
runtime: video.compactVideoRenderer.lengthText.runs[0].text,
|
||||
uploaded: video.compactVideoRenderer.publishedTimeText.runs[0].text,
|
||||
views: video.compactVideoRenderer.viewCountText.runs[0].text,
|
||||
thumbnails: video.compactVideoRenderer.thumbnail.thumbnails
|
||||
})
|
||||
} else {
|
||||
//--- If Entry Is Not A Video ---//
|
||||
//logger(constants.LOGGER_NAMES.search, { type: "Error Caught Successfully", error: video }, true);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
callback(results);
|
||||
|
||||
},
|
||||
|
||||
getRemainingVideoInfo(id, callback) {
|
||||
String.prototype.decodeEscapeSequence = function() {
|
||||
return this.replace(/\\x([0-9A-Fa-f]{2})/g, function() {
|
||||
return String.fromCharCode(parseInt(arguments[1], 16));
|
||||
});
|
||||
getRemainingVideoInfo(id, callback) {
|
||||
String.prototype.decodeEscapeSequence = function () {
|
||||
return this.replace(/\\x([0-9A-Fa-f]{2})/g, function () {
|
||||
return String.fromCharCode(parseInt(arguments[1], 16));
|
||||
});
|
||||
};
|
||||
Http.request({
|
||||
method: "GET",
|
||||
url: `${constants.URLS.YT_URL}/watch`,
|
||||
params: { v: id },
|
||||
})
|
||||
.then((res) => {
|
||||
let dataUpdated = res.data.decodeEscapeSequence();
|
||||
let likes = dataUpdated
|
||||
.split(
|
||||
`"defaultIcon":{"iconType":"LIKE"},"defaultText":{"runs":[{"text":"`
|
||||
)[1]
|
||||
.split(`"}],"accessibility":`)[0];
|
||||
let uploadDate = dataUpdated
|
||||
.split(`"uploadDate":"`)[1]
|
||||
.split(`}},"trackingParams":"`)[0]
|
||||
.slice(0, -2);
|
||||
let data = {
|
||||
likes: likes,
|
||||
uploadDate: uploadDate,
|
||||
};
|
||||
Http.request({
|
||||
method: 'GET',
|
||||
url: `${constants.URLS.YT_URL}/watch`,
|
||||
params: { v: id }
|
||||
})
|
||||
.then((res) => {
|
||||
let dataUpdated = res.data.decodeEscapeSequence()
|
||||
let likes = dataUpdated.split(`"defaultIcon":{"iconType":"LIKE"},"defaultText":{"runs":[{"text":"`)[1].split(`"}],"accessibility":`)[0]
|
||||
let uploadDate = dataUpdated.split(`"uploadDate":"`)[1].split(`}},"trackingParams":"`)[0].slice(0, -2);
|
||||
let data = {
|
||||
"likes": likes,
|
||||
"uploadDate": uploadDate
|
||||
}
|
||||
logger("vidData", data)
|
||||
callback(data)
|
||||
})
|
||||
.catch((err) => {
|
||||
logger("codeRun", err, true);
|
||||
callback(err);
|
||||
});
|
||||
logger("vidData", data);
|
||||
callback(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger("codeRun", err, true);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
getReturnYoutubeDislike(id, callback) {
|
||||
Http.request({
|
||||
method: 'GET',
|
||||
url: `https://returnyoutubedislikeapi.com/votes`,
|
||||
params: { videoId: id }
|
||||
})
|
||||
.then((res) => {
|
||||
logger("rydData", res.data)
|
||||
callback(res.data)
|
||||
})
|
||||
.catch((err) => {
|
||||
logger("codeRun", err, true);
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
getReturnYoutubeDislike(id, callback) {
|
||||
Http.request({
|
||||
method: "GET",
|
||||
url: `https://returnyoutubedislikeapi.com/votes`,
|
||||
params: { videoId: id },
|
||||
})
|
||||
.then((res) => {
|
||||
logger("rydData", res.data);
|
||||
callback(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
logger("codeRun", err, true);
|
||||
callback(err);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
//--- Recommendations ---//
|
||||
|
||||
|
@ -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)
|
||||
// These are just a way for the backend Javascript to communicate with the front end Vue scripts. Essentially a wrapper inside a wrapper
|
||||
const innertubeModule = {
|
||||
async getAPI() {
|
||||
if (!InnertubeAPI) {
|
||||
InnertubeAPI = await Innertube.createAsync((message, isError) => {
|
||||
logger(constants.LOGGER_NAMES.innertube, message, isError);
|
||||
});
|
||||
}
|
||||
return InnertubeAPI;
|
||||
},
|
||||
|
||||
async getAPI() {
|
||||
if (!InnertubeAPI) {
|
||||
InnertubeAPI = await Innertube.createAsync((message, isError) => { logger(constants.LOGGER_NAMES.innertube, message, isError); })
|
||||
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;
|
||||
}
|
||||
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 ---//
|
||||
export default ({ app }, inject) => {
|
||||
inject('youtube', {...searchModule, ...innertubeModule })
|
||||
inject("logger", logger)
|
||||
}
|
||||
inject("youtube", { ...searchModule, ...innertubeModule });
|
||||
inject("logger", logger);
|
||||
};
|
||||
logger(constants.LOGGER_NAMES.init, "Program Started");
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
export const state = () => ({
|
||||
roundTweak: 0
|
||||
})
|
||||
roundTweak: 0,
|
||||
});
|
||||
export const mutations = {
|
||||
initTweaks(state) {
|
||||
// NOTE: localStorage is not reactive, so it will only be used on first load
|
||||
// currently called beforeCreate() in pages/default.vue
|
||||
if (process.client) {
|
||||
state.roundTweak = localStorage.getItem("roundTweak") || 0
|
||||
}
|
||||
},
|
||||
setRoundTweak (state, payload) {
|
||||
if (!isNaN(payload)) {
|
||||
state.roundTweak = payload
|
||||
localStorage.setItem("roundTweak", payload)
|
||||
}
|
||||
initTweaks(state) {
|
||||
// NOTE: localStorage is not reactive, so it will only be used on first load
|
||||
// currently called beforeCreate() in pages/default.vue
|
||||
if (process.client) {
|
||||
state.roundTweak = localStorage.getItem("roundTweak") || 0;
|
||||
}
|
||||
}
|
||||
},
|
||||
setRoundTweak(state, payload) {
|
||||
if (!isNaN(payload)) {
|
||||
state.roundTweak = payload;
|
||||
localStorage.setItem("roundTweak", payload);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
1
scripts/install.sh
Normal file
1
scripts/install.sh
Normal file
|
@ -0,0 +1 @@
|
|||
npm i; cd NUXT/; npm i; cd ..;
|
Loading…
Reference in a new issue