mirror of
https://github.com/VueTubeApp/VueTube
synced 2024-11-29 14:43:04 +00:00
refactor: changed to web endpoint because youtube is breaking the android JSON API
This commit is contained in:
parent
6d9df0dfbf
commit
25fbeab72f
11 changed files with 431 additions and 75 deletions
87
NUXT/components/Comments/commentMainRenderer.vue
Normal file
87
NUXT/components/Comments/commentMainRenderer.vue
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
<template>
|
||||||
|
<div id="comment">
|
||||||
|
<a
|
||||||
|
:href="this.$rendererUtils.getNavigationEndpoints(comment.authorEndpoint)"
|
||||||
|
class="avatar-link pt-2"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
class="avatar-thumbnail"
|
||||||
|
:src="
|
||||||
|
comment.authorThumbnail.thumbnails[
|
||||||
|
comment.authorThumbnail.thumbnails.length - 1
|
||||||
|
].url
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<v-card-text class="comment-info pt-2">
|
||||||
|
<div
|
||||||
|
v-for="title in comment.title.runs"
|
||||||
|
:key="title.text"
|
||||||
|
style="margin-top: 0.5em"
|
||||||
|
class="vid-title"
|
||||||
|
>
|
||||||
|
{{ title.text }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="caption background--text"
|
||||||
|
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||||
|
v-text="parseBottom(comment)"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.entry {
|
||||||
|
width: 100%; /* Prevent Loading Weirdness */
|
||||||
|
}
|
||||||
|
|
||||||
|
.vid-title {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-thumbnail {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-basis: auto;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (orientation: landscape) {
|
||||||
|
.entry {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
#details {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ["comment"],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
parseBottom(comment) {
|
||||||
|
const bottomText = [
|
||||||
|
comment.subscriberCountText?.runs[0].text,
|
||||||
|
comment.videoCountText?.runs.map((run) => run.text).join(" "),
|
||||||
|
];
|
||||||
|
return bottomText.join(" · ");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
0
NUXT/components/Comments/commentThreadRenderer.vue
Normal file
0
NUXT/components/Comments/commentThreadRenderer.vue
Normal file
|
@ -32,7 +32,7 @@
|
||||||
<a
|
<a
|
||||||
:href="
|
:href="
|
||||||
this.$rendererUtils.getNavigationEndpoints(
|
this.$rendererUtils.getNavigationEndpoints(
|
||||||
video.shortBylineText.runs[0].navigationEndpoint
|
video.shortBylineText.runs[0]
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
class="avatar-link pt-2"
|
class="avatar-link pt-2"
|
||||||
|
|
|
@ -38,12 +38,14 @@
|
||||||
import compactVideoRenderer from "~/components/CompactRenderers/compactVideoRenderer.vue";
|
import compactVideoRenderer from "~/components/CompactRenderers/compactVideoRenderer.vue";
|
||||||
import compactChannelRenderer from "~/components/CompactRenderers/compactChannelRenderer.vue";
|
import compactChannelRenderer from "~/components/CompactRenderers/compactChannelRenderer.vue";
|
||||||
import gridVideoRenderer from "~/components/gridRenderers/gridVideoRenderer.vue";
|
import gridVideoRenderer from "~/components/gridRenderers/gridVideoRenderer.vue";
|
||||||
|
import videoWithContextRenderer from "~/components/gridRenderers/videoWithContextRenderer.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
gridVideoRenderer,
|
gridVideoRenderer,
|
||||||
compactVideoRenderer,
|
compactVideoRenderer,
|
||||||
compactChannelRenderer,
|
compactChannelRenderer,
|
||||||
|
videoWithContextRenderer,
|
||||||
},
|
},
|
||||||
props: ["render"],
|
props: ["render"],
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
<template v-for="(text, index) in render.description.runs">
|
<template v-for="(text, index) in render.descriptionBodyText.runs">
|
||||||
<template
|
<template v-if="$rendererUtils.checkInternal(text)">
|
||||||
v-if="
|
|
||||||
text.navigationEndpoint && text.navigationEndpoint.webviewEndpoint
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<a
|
<a
|
||||||
@click="openExternal($rendererUtils.getNavigationEndpoints(text))"
|
@click="openInternal($rendererUtils.getNavigationEndpoints(text))"
|
||||||
:key="index"
|
:key="index"
|
||||||
>{{ text.text }}</a
|
>{{ text.text }}</a
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="$rendererUtils.checkInternal(text)">
|
<template
|
||||||
|
v-else-if="
|
||||||
|
text.navigationEndpoint && text.navigationEndpoint.urlEndpoint
|
||||||
|
"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
@click="openInternal($rendererUtils.getNavigationEndpoints(text))"
|
@click="openExternal($rendererUtils.getNavigationEndpoints(text))"
|
||||||
:key="index"
|
:key="index"
|
||||||
>{{ text.text }}</a
|
>{{ text.text }}</a
|
||||||
>
|
>
|
||||||
|
@ -28,6 +28,7 @@
|
||||||
.description {
|
.description {
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
151
NUXT/components/gridRenderers/videoWithContextRenderer.vue
Normal file
151
NUXT/components/gridRenderers/videoWithContextRenderer.vue
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<v-card
|
||||||
|
class="entry videoWithContextRenderer background"
|
||||||
|
:to="`/watch?v=${video.videoId}`"
|
||||||
|
flat
|
||||||
|
>
|
||||||
|
<div style="position: relative" class="thumbnail-container">
|
||||||
|
<v-img
|
||||||
|
v-if="video.thumbnail"
|
||||||
|
:aspect-ratio="16 / 9"
|
||||||
|
:src="
|
||||||
|
$youtube.getThumbnail(
|
||||||
|
video.videoId,
|
||||||
|
'max',
|
||||||
|
video.thumbnail.thumbnails
|
||||||
|
)
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
v-if="video.thumbnailOverlays"
|
||||||
|
class="videoRuntimeFloat"
|
||||||
|
:class="
|
||||||
|
'style-' +
|
||||||
|
video.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer.style
|
||||||
|
"
|
||||||
|
style="color: #fff"
|
||||||
|
v-text="
|
||||||
|
video.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer.text
|
||||||
|
.runs[0].text
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div id="details">
|
||||||
|
<a
|
||||||
|
:href="
|
||||||
|
$rendererUtils.getNavigationEndpoints(
|
||||||
|
video.shortBylineText.runs[0].navigationEndpoint
|
||||||
|
)
|
||||||
|
"
|
||||||
|
class="avatar-link pt-2"
|
||||||
|
>
|
||||||
|
<v-img
|
||||||
|
class="avatar-thumbnail"
|
||||||
|
:src="
|
||||||
|
video.channelThumbnail.channelThumbnailWithLinkRenderer.thumbnail
|
||||||
|
.thumbnails[0].url
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<v-card-text class="video-info pt-2">
|
||||||
|
<div
|
||||||
|
v-for="title in video.headline.runs"
|
||||||
|
:key="title.text"
|
||||||
|
style="margin-top: 0.5em"
|
||||||
|
class="font-weight-medium vid-title"
|
||||||
|
>
|
||||||
|
{{ title.text }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="background--text text--lighten-5 caption"
|
||||||
|
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||||
|
v-text="parseBottom(video)"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.entry {
|
||||||
|
width: 100%; /* Prevent Loading Weirdness */
|
||||||
|
}
|
||||||
|
.videoRuntimeFloat {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 10px;
|
||||||
|
right: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0px 4px 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoRuntimeFloat.style-DEFAULT {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.videoRuntimeFloat.style-LIVE {
|
||||||
|
background: rgba(255, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vid-title {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-thumbnail {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-basis: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (orientation: landscape) {
|
||||||
|
.entry {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.thumbnail-container {
|
||||||
|
width: 50vh;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
#details {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
.avatar-thumbnail {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
.video-info {
|
||||||
|
padding-top: 0 !important;
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ["video"],
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
parseBottom(video) {
|
||||||
|
const bottomText = [
|
||||||
|
video.shortBylineText?.runs[0].text,
|
||||||
|
video.shortViewCountText?.runs[0].text,
|
||||||
|
];
|
||||||
|
if (video.publishedTimeText?.runs[0].text)
|
||||||
|
bottomText.push(video.publishedTimeText?.runs[0].text);
|
||||||
|
return bottomText.join(" · ");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -19,11 +19,16 @@
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
"
|
"
|
||||||
v-text="title"
|
v-text="video.title"
|
||||||
/>
|
/>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<div style="margin-bottom: 1rem">
|
<div style="margin-bottom: 1rem">
|
||||||
{{ views }} views • {{ uploaded }}
|
<template
|
||||||
|
v-for="text in video.metadata.contents.find(
|
||||||
|
(content) => content.slimVideoInformationRenderer
|
||||||
|
).slimVideoInformationRenderer.collapsedSubtitle.runs"
|
||||||
|
>{{ text.text }}</template
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scrolling Div For Interactions --->
|
<!-- Scrolling Div For Interactions --->
|
||||||
|
@ -58,12 +63,7 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- End Scrolling Div For Interactions --->
|
<!-- End Scrolling Div For Interactions --->
|
||||||
<!-- <hr /> -->
|
<!-- <hr /> -->
|
||||||
<p>Channel Stuff</p>
|
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<div v-if="showMore" class="scroll-y ml-2 mr-2">
|
|
||||||
<slim-video-description-renderer :render="description" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- <v-bottom-sheet
|
<!-- <v-bottom-sheet
|
||||||
v-model="showMore"
|
v-model="showMore"
|
||||||
color="background"
|
color="background"
|
||||||
|
@ -75,32 +75,88 @@
|
||||||
><br />
|
><br />
|
||||||
|
|
||||||
<div class="scroll-y">
|
<div class="scroll-y">
|
||||||
{{ description }}
|
{{ response.renderedData.description }}
|
||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
</v-bottom-sheet> -->
|
</v-bottom-sheet> -->
|
||||||
<!-- <v-bottom-sheet v-model="share" color="background" style="z-index: 9999999">
|
<!-- <v-bottom-sheet v-model="share" color="background" style="z-index: 9999999">
|
||||||
<v-sheet style="padding: 1em">
|
<v-sheet style="padding: 1em">
|
||||||
<div class="scroll-y">
|
<div class="scroll-y">
|
||||||
{{ description }}
|
{{ response.renderedData.description }}
|
||||||
</div>
|
</div>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
</v-bottom-sheet> -->
|
</v-bottom-sheet> -->
|
||||||
</v-card>
|
</v-card>
|
||||||
<vid-load-renderer v-if="!recommends" />
|
<v-divider />
|
||||||
<shelf-renderer v-else :render="recommends" />
|
|
||||||
|
<!-- Channel Bar -->
|
||||||
|
<v-card
|
||||||
|
class="channel-section background"
|
||||||
|
v-if="loaded"
|
||||||
|
:to="video.channelUrl"
|
||||||
|
>
|
||||||
|
<div id="details">
|
||||||
|
<div class="avatar-link mr-3">
|
||||||
|
<v-img class="avatar-thumbnail" :src="video.channelImg" />
|
||||||
|
</div>
|
||||||
|
<div class="channel-byline">
|
||||||
|
<div class="channel-name" v-text="video.channelName" />
|
||||||
|
<div
|
||||||
|
class="caption background--text"
|
||||||
|
:class="$vuetify.theme.dark ? 'text--lighten-4' : 'text--darken-4'"
|
||||||
|
v-text="video.channelSubs"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="channel-buttons"
|
||||||
|
style="color: rgb(204, 0, 0); text-transform: uppercase"
|
||||||
|
>
|
||||||
|
subscribe
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
<v-divider />
|
||||||
|
|
||||||
|
<!-- Comments -->
|
||||||
|
<!-- <v-card flat class="background comment-button" v-if="loaded">
|
||||||
|
<v-text
|
||||||
|
>{{ video.commentData.title }} •
|
||||||
|
{{ video.commentData.commentsCount }}</v-text
|
||||||
|
>
|
||||||
|
</v-card>
|
||||||
|
<v-divider /> -->
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<div v-if="showMore" class="scroll-y ml-4 mr-4">
|
||||||
|
<slim-video-description-renderer
|
||||||
|
:render="video.renderedData.description"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Related Videos -->
|
||||||
|
<div class="loaders" v-if="!loaded">
|
||||||
|
<v-skeleton-loader
|
||||||
|
type="list-item-two-line, actions, divider, list-item-avatar, divider, list-item-three-line"
|
||||||
|
/>
|
||||||
|
<vid-load-renderer :count="5" />
|
||||||
|
</div>
|
||||||
|
<item-section-renderer v-else :render="recommends" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Share } from "@capacitor/share";
|
import { Share } from "@capacitor/share";
|
||||||
import ShelfRenderer from "~/components/SectionRenderers/shelfRenderer.vue";
|
|
||||||
import VidLoadRenderer from "~/components/vidLoadRenderer.vue";
|
import VidLoadRenderer from "~/components/vidLoadRenderer.vue";
|
||||||
import { getCpn } from "~/plugins/utils";
|
import { getCpn } from "~/plugins/utils";
|
||||||
import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDescriptionRenderer.vue";
|
import SlimVideoDescriptionRenderer from "~/components/UtilRenderers/slimVideoDescriptionRenderer.vue";
|
||||||
|
import ItemSectionRenderer from "~/components/SectionRenderers/itemSectionRenderer.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ShelfRenderer, VidLoadRenderer, SlimVideoDescriptionRenderer },
|
components: {
|
||||||
|
VidLoadRenderer,
|
||||||
|
SlimVideoDescriptionRenderer,
|
||||||
|
ItemSectionRenderer,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
interactions: [
|
interactions: [
|
||||||
|
@ -129,15 +185,12 @@ export default {
|
||||||
],
|
],
|
||||||
showMore: false,
|
showMore: false,
|
||||||
// share: false,
|
// share: false,
|
||||||
title: null,
|
|
||||||
uploaded: null,
|
|
||||||
vidSrc: null,
|
vidSrc: null,
|
||||||
sources: [],
|
sources: [],
|
||||||
description: null,
|
|
||||||
views: null,
|
|
||||||
recommends: null,
|
recommends: null,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
interval: null,
|
interval: null,
|
||||||
|
video: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -167,10 +220,10 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getVideo() {
|
getVideo() {
|
||||||
this.likes = 100;
|
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
|
|
||||||
this.$youtube.getVid(this.$route.query.v).then((result) => {
|
this.$youtube.getVid(this.$route.query.v).then((result) => {
|
||||||
|
this.video = result;
|
||||||
console.log("Video info data", result);
|
console.log("Video info data", result);
|
||||||
console.log(result.availableResolutions);
|
console.log(result.availableResolutions);
|
||||||
|
|
||||||
|
@ -184,14 +237,9 @@ export default {
|
||||||
].url; // Takes the highest available resolution with both video and Audio. Note this will be lower than the actual highest resolution
|
].url; // Takes the highest available resolution with both video and Audio. Note this will be lower than the actual highest resolution
|
||||||
|
|
||||||
//--- Content Stuff ---//
|
//--- Content Stuff ---//
|
||||||
this.title = result.title;
|
|
||||||
this.description = result.renderedData.description; // While this works, I do recommend using the rendered description instead in the future as there are some things a pure string wouldn't work with
|
|
||||||
this.views = parseInt(result.metadata.viewCount).toLocaleString();
|
|
||||||
this.likes = result.metadata.likes.toLocaleString();
|
this.likes = result.metadata.likes.toLocaleString();
|
||||||
this.uploaded = result.metadata.uploadDate;
|
|
||||||
this.interactions[0].value = result.metadata.likes.toLocaleString();
|
this.interactions[0].value = result.metadata.likes.toLocaleString();
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
||||||
this.recommends = result.renderedData.recommendations;
|
this.recommends = result.renderedData.recommendations;
|
||||||
// .catch((error) => this.$logger("Watch", error, true));
|
// .catch((error) => this.$logger("Watch", error, true));
|
||||||
console.log("recommendations:", this.recommends);
|
console.log("recommendations:", this.recommends);
|
||||||
|
@ -220,8 +268,8 @@ export default {
|
||||||
async share() {
|
async share() {
|
||||||
// this.share = !this.share;
|
// this.share = !this.share;
|
||||||
await Share.share({
|
await Share.share({
|
||||||
title: this.title,
|
title: this.video.title,
|
||||||
text: this.title,
|
text: this.video.title,
|
||||||
url: "https://youtu.be/" + this.$route.query.v,
|
url: "https://youtu.be/" + this.$route.query.v,
|
||||||
dialogTitle: "Share video",
|
dialogTitle: "Share video",
|
||||||
});
|
});
|
||||||
|
@ -271,4 +319,34 @@ export default {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.channel-section,
|
||||||
|
.comment-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-section #details {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-section .channel-byline {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-section .avatar-thumbnail {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 35px;
|
||||||
|
height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-section .channel-name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,7 +12,7 @@ const url = {
|
||||||
const ytApiVal = {
|
const ytApiVal = {
|
||||||
VERSION: "16.25",
|
VERSION: "16.25",
|
||||||
CLIENTNAME: "ANDROID",
|
CLIENTNAME: "ANDROID",
|
||||||
VERSION_WEB: "2.20220318.00.00",
|
VERSION_WEB: "2.20220331.06.00",
|
||||||
CLIENT_WEB: 2,
|
CLIENT_WEB: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import { Http } from "@capacitor-community/http";
|
import { Http } from "@capacitor-community/http";
|
||||||
import { getBetweenStrings } from "./utils";
|
import { getBetweenStrings } from "./utils";
|
||||||
|
import rendererUtils from "./renderers";
|
||||||
import constants from "./constants";
|
import constants from "./constants";
|
||||||
|
|
||||||
class Innertube {
|
class Innertube {
|
||||||
|
@ -95,8 +96,11 @@ class Innertube {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getContinuationsAsync(continuation, type) {
|
async getContinuationsAsync(continuation, type, contextAdditional = {}) {
|
||||||
let data = { context: this.context, continuation: continuation };
|
let data = {
|
||||||
|
context: { ...this.context, ...contextAdditional },
|
||||||
|
continuation: continuation,
|
||||||
|
};
|
||||||
let url;
|
let url;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "browse":
|
case "browse":
|
||||||
|
@ -135,7 +139,17 @@ class Innertube {
|
||||||
let data = { context: this.context, videoId: id };
|
let data = { context: this.context, videoId: id };
|
||||||
const responseNext = await Http.post({
|
const responseNext = await Http.post({
|
||||||
url: `${constants.URLS.YT_BASE_API}/next?key=${this.key}`,
|
url: `${constants.URLS.YT_BASE_API}/next?key=${this.key}`,
|
||||||
data: data,
|
data: {
|
||||||
|
...data,
|
||||||
|
...{
|
||||||
|
context: {
|
||||||
|
client: {
|
||||||
|
clientName: constants.YT_API_VALUES.CLIENT_WEB,
|
||||||
|
clientVersion: constants.YT_API_VALUES.VERSION_WEB,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
headers: constants.INNERTUBE_HEADER(this.context.client),
|
headers: constants.INNERTUBE_HEADER(this.context.client),
|
||||||
}).catch((error) => error);
|
}).catch((error) => error);
|
||||||
|
|
||||||
|
@ -251,59 +265,67 @@ class Innertube {
|
||||||
const resolutions = responseInfo.streamingData;
|
const resolutions = responseInfo.streamingData;
|
||||||
const columnUI =
|
const columnUI =
|
||||||
responseNext.contents.singleColumnWatchNextResults.results.results;
|
responseNext.contents.singleColumnWatchNextResults.results.results;
|
||||||
|
const vidMetadata = columnUI.contents.find(
|
||||||
|
(content) => content.slimVideoMetadataSectionRenderer
|
||||||
|
).slimVideoMetadataSectionRenderer;
|
||||||
|
|
||||||
|
const recommendations = columnUI?.contents.find(
|
||||||
|
(contents) => contents?.itemSectionRenderer?.targetId == "watch-next-feed"
|
||||||
|
).itemSectionRenderer;
|
||||||
|
|
||||||
|
const ownerData = vidMetadata.contents.find(
|
||||||
|
(content) => content.slimOwnerRenderer
|
||||||
|
)?.slimOwnerRenderer;
|
||||||
|
|
||||||
const vidData = {
|
const vidData = {
|
||||||
id: details.videoId,
|
id: details.videoId,
|
||||||
title: details.title,
|
title: details.title,
|
||||||
isLive: details.isLiveContent,
|
isLive: details.isLiveContent,
|
||||||
channelName: details.author,
|
channelName: details.author,
|
||||||
channelUrl:
|
channelSubs: ownerData?.collapsedSubtitle?.runs[0]?.text,
|
||||||
columnUI?.contents[0].slimVideoMetadataSectionRenderer?.contents.find(
|
channelUrl: rendererUtils.getNavigationEndpoints(ownerData),
|
||||||
(contents) => contents.elementRenderer
|
channelImg: ownerData?.thumbnail?.thumbnails[0].url,
|
||||||
)?.newElement?.type?.componentType?.model?.channelBarModel
|
|
||||||
?.videoChannelBarData?.onTap?.innertubeCommand?.browseEndpoint
|
|
||||||
?.canonicalBaseUrl,
|
|
||||||
channelImg:
|
|
||||||
columnUI?.contents[0].slimVideoMetadataSectionRenderer?.contents.find(
|
|
||||||
(contents) => contents.elementRenderer
|
|
||||||
)?.newElement?.type?.componentType?.model?.channelBarModel
|
|
||||||
?.videoChannelBarData?.avatar?.image?.sources[0].url,
|
|
||||||
availableResolutions: resolutions?.formats,
|
availableResolutions: resolutions?.formats,
|
||||||
availableResolutionsAdaptive: resolutions?.adaptiveFormats,
|
availableResolutionsAdaptive: resolutions?.adaptiveFormats,
|
||||||
metadata: {
|
metadata: {
|
||||||
|
contents: vidMetadata.contents,
|
||||||
description: details.shortDescription,
|
description: details.shortDescription,
|
||||||
thumbnails: details.thumbnails?.thumbnails,
|
thumbnails: details.thumbnails?.thumbnails,
|
||||||
uploadDate:
|
|
||||||
columnUI?.contents[0].slimVideoMetadataSectionRenderer?.contents.find(
|
|
||||||
(contents) => contents.slimVideoDescriptionRenderer
|
|
||||||
)?.slimVideoDescriptionRenderer.publishDate.runs[0].text,
|
|
||||||
isPrivate: details.isPrivate,
|
isPrivate: details.isPrivate,
|
||||||
viewCount: details.viewCount,
|
viewCount: details.viewCount,
|
||||||
lengthSeconds: details.lengthSeconds,
|
lengthSeconds: details.lengthSeconds,
|
||||||
likes: parseInt(
|
likes: parseInt(
|
||||||
columnUI?.contents[0].slimVideoMetadataSectionRenderer?.contents
|
vidMetadata.contents
|
||||||
.find((contents) => contents.slimVideoScrollableActionBarRenderer)
|
.find((content) => content.slimVideoActionBarRenderer)
|
||||||
?.slimVideoScrollableActionBarRenderer.buttons.find(
|
.slimVideoActionBarRenderer.buttons.find(
|
||||||
(button) => button.slimMetadataToggleButtonRenderer.isLike == true
|
(button) => button.slimMetadataToggleButtonRenderer.isLike
|
||||||
)
|
)
|
||||||
?.slimMetadataToggleButtonRenderer?.button.toggleButtonRenderer?.defaultText?.accessibility?.accessibilityData?.label?.replace(
|
.slimMetadataToggleButtonRenderer.button.toggleButtonRenderer.defaultText.accessibility.accessibilityData.label.replace(
|
||||||
/\D/g,
|
/\D/g,
|
||||||
""
|
""
|
||||||
)
|
)
|
||||||
), // Yes. I know.
|
), // Yes. I know.
|
||||||
},
|
},
|
||||||
renderedData: {
|
renderedData: {
|
||||||
description: columnUI?.contents
|
description: responseNext.engagementPanels
|
||||||
.find((contents) => contents.slimVideoMetadataSectionRenderer)
|
.find(
|
||||||
.slimVideoMetadataSectionRenderer?.contents.find(
|
(panel) =>
|
||||||
(contents) => contents.slimVideoDescriptionRenderer
|
panel.engagementPanelSectionListRenderer.panelIdentifier ==
|
||||||
)?.slimVideoDescriptionRenderer,
|
"video-description-ep-identifier"
|
||||||
recommendations: columnUI?.contents.find(
|
)
|
||||||
(contents) => contents.shelfRenderer
|
.engagementPanelSectionListRenderer.content.structuredDescriptionContentRenderer.items.find(
|
||||||
).shelfRenderer,
|
(item) => item.expandableVideoDescriptionBodyRenderer
|
||||||
|
).expandableVideoDescriptionBodyRenderer,
|
||||||
|
recommendations: recommendations,
|
||||||
recommendationsContinuation:
|
recommendationsContinuation:
|
||||||
columnUI?.continuations[0].reloadContinuationData?.continuation,
|
recommendations.contents[recommendations.contents.length - 1]
|
||||||
|
.continuationItemRenderer?.continuationEndpoint.continuationCommand
|
||||||
|
.token,
|
||||||
},
|
},
|
||||||
|
// commentData:
|
||||||
|
// responseNext?.frameworkUpdates?.entityBatchUpdate?.mutations?.find(
|
||||||
|
// (mutations) => mutations.payload?.commentHeaderEntityPayload
|
||||||
|
// ).payload.commentHeaderEntityPayload,
|
||||||
playbackTracking: responseInfo.playbackTracking,
|
playbackTracking: responseInfo.playbackTracking,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,24 +3,33 @@ class rendererUtils {
|
||||||
static getNavigationEndpoints(base) {
|
static getNavigationEndpoints(base) {
|
||||||
const navEndpoint = base.navigationEndpoint;
|
const navEndpoint = base.navigationEndpoint;
|
||||||
if (!navEndpoint) return;
|
if (!navEndpoint) return;
|
||||||
if (navEndpoint.webviewEndpoint) {
|
if (navEndpoint.urlEndpoint) {
|
||||||
return navEndpoint.webviewEndpoint.url;
|
const params = new Proxy(
|
||||||
|
new URLSearchParams(navEndpoint.urlEndpoint.url),
|
||||||
|
{
|
||||||
|
get: (searchParams, prop) => searchParams.get(prop),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (params.q) return decodeURI(params.q);
|
||||||
|
else return new URL(navEndpoint.urlEndpoint.url).pathname;
|
||||||
} else if (navEndpoint.browseEndpoint) {
|
} else if (navEndpoint.browseEndpoint) {
|
||||||
return navEndpoint.browseEndpoint.canonicalBaseUrl;
|
return navEndpoint.browseEndpoint.canonicalBaseUrl;
|
||||||
} else if (navEndpoint.watchEndpoint) {
|
} else if (navEndpoint.watchEndpoint) {
|
||||||
return `/watch?v=${navEndpoint.watchEndpoint.videoId}`;
|
return `/watch?v=${navEndpoint.watchEndpoint.videoId}`;
|
||||||
} else if (navEndpoint.navigationEndpoint) {
|
} else if (navEndpoint.navigationEndpoint) {
|
||||||
return; //for now
|
return; //for now
|
||||||
|
} else if (navEndpoint.searchEndpoint) {
|
||||||
|
return `/search?q=${encodeURI(navEndpoint.searchEndpoint.query)}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkInternal(base) {
|
static checkInternal(base) {
|
||||||
const navEndpoint = base.navigationEndpoint;
|
const tmp = document.createElement("a");
|
||||||
if (!navEndpoint) return false;
|
tmp.href = this.getNavigationEndpoints(base);
|
||||||
if (navEndpoint.browseEndpoint || navEndpoint.watchEndpoint) {
|
if (tmp.host !== window.location.host || !base.navigationEndpoint) {
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,15 @@ function getCpn() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMutationByKey(key, mutations) {
|
||||||
|
if (!key || !mutations) return undefined;
|
||||||
|
return mutations.find((mutation) => mutation.entityKey === key).payload;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getBetweenStrings,
|
getBetweenStrings,
|
||||||
hexToRgb,
|
hexToRgb,
|
||||||
rgbToHex,
|
rgbToHex,
|
||||||
getCpn,
|
getCpn,
|
||||||
|
getMutationByKey,
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue