mirror of https://github.com/VueTubeApp/VueTube
Merge branch 'main' of https://github.com/VueTubeApp/VueTube into main
This commit is contained in:
commit
7f8de63222
|
@ -0,0 +1,16 @@
|
|||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/NUXT/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
|
@ -15,9 +15,9 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Install dependencies
|
||||
|
@ -29,7 +29,7 @@ jobs:
|
|||
working-directory: NUXT
|
||||
run: npm run generate
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
@ -39,20 +39,20 @@ jobs:
|
|||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Set up Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Install dependencies
|
||||
run: npm i
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Copy web assets to native platform
|
||||
|
@ -63,7 +63,7 @@ jobs:
|
|||
working-directory: android
|
||||
run: chmod +x gradlew; ./gradlew clean assembleRelease -x test -Pandroid.injected.signing.store.file=/home/runner/work/VueTube/VueTube/android/key.jks -Pandroid.injected.signing.store.password=${{ secrets.ANDROID_STORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.ANDROID_KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: android
|
||||
path: android/app/build/outputs/apk/release/app-release.apk
|
||||
|
@ -74,14 +74,14 @@ jobs:
|
|||
needs: [build]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
- name: Set up Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
- name: Install dependencies
|
||||
|
@ -109,7 +109,7 @@ jobs:
|
|||
run: mkdir Payload && mv ~/Library/Developer/Xcode/DerivedData/App-*/Build/Products/Debug-iphoneos/App.app/ Payload && zip -r Payload.zip Payload && mv Payload.zip VueTube.ipa
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: iOS
|
||||
path: VueTube.ipa
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="author-comment-badge-renderer"
|
||||
v-if="metadata && iconTypeMap.hasOwnProperty(metadata.icon.iconType)"
|
||||
v-if="metadata && iconTypeMap.hasOwnProperty(metadata.icon)"
|
||||
>
|
||||
<v-tooltip top>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
},
|
||||
}"
|
||||
mediagroup="vuetubecute"
|
||||
autoplay
|
||||
width="100%"
|
||||
:src="vidSrc"
|
||||
:height="isFullscreen ? '100%' : 'auto'"
|
||||
|
@ -380,7 +379,7 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
recommends: {
|
||||
type: Array,
|
||||
type: Object,
|
||||
default: () => {
|
||||
return [];
|
||||
},
|
||||
|
@ -416,15 +415,78 @@ export default {
|
|||
mounted() {
|
||||
console.log("sources", this.sources);
|
||||
console.log("recommends", this.recommends);
|
||||
console.log("video", this.video);
|
||||
this.vid = this.$refs.player;
|
||||
this.aud = this.$refs.audio;
|
||||
|
||||
// TODO: this.$store.state.player.quality, check if exists and select the closest one
|
||||
if (this.$store.state.player.preload) this.prebuffer(this.sources[5].url);
|
||||
else {
|
||||
this.audSrc = this.sources[this.sources.length - 1].url;
|
||||
this.vidSrc = this.sources[5].url;
|
||||
/**
|
||||
* Video quality selection which device can play normally
|
||||
* @returns {number[]}
|
||||
*/
|
||||
function getPreferredQuality() {
|
||||
let width;
|
||||
let height;
|
||||
// Detecting device - https://stackoverflow.com/a/11381730/18543384
|
||||
window.mobileCheck = function () {
|
||||
let check = false;
|
||||
(function (a) {
|
||||
if (
|
||||
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(
|
||||
a
|
||||
) ||
|
||||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(
|
||||
a.substr(0, 4)
|
||||
)
|
||||
)
|
||||
check = true;
|
||||
})(navigator.userAgent || navigator.vendor || window.opera);
|
||||
return check;
|
||||
};
|
||||
// The smartphone screen width is smaller than the length (1080x1920).
|
||||
// On a computer, the opposite is true (1920x1080).
|
||||
// Detecting smaller side of the display.
|
||||
if (window.mobileCheck) {
|
||||
width = window.screen.width * window.devicePixelRatio;
|
||||
height = window.screen.height * window.devicePixelRatio;
|
||||
} else {
|
||||
width = window.screen.height * window.devicePixelRatio;
|
||||
height = window.screen.width * window.devicePixelRatio;
|
||||
}
|
||||
return [width, height];
|
||||
}
|
||||
|
||||
// TODO: this.$store.state.player.quality, check if exists and select the closest one
|
||||
let displayInfo = getPreferredQuality();
|
||||
let indexOfPreferredQuality = 0;
|
||||
console.warn(displayInfo);
|
||||
for (let i = this.sources.length; i > 0; i--) {
|
||||
if (i === this.sources.length) continue;
|
||||
else {
|
||||
// if quality height <= to the smaller side of the display,
|
||||
// write the index of the source to a variable indexOfPreferredQuality.
|
||||
if (
|
||||
this.sources[i].height <= displayInfo[1] &&
|
||||
this.sources[i].mimeType.includes("video")
|
||||
) {
|
||||
indexOfPreferredQuality = i;
|
||||
}
|
||||
if (this.sources[i].mimeType.indexOf("audio") > -1) {
|
||||
this.audSrc = this.sources[i].url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.vidSrc = this.sources[indexOfPreferredQuality].url;
|
||||
// this.prebuffer(this.sources[indexOfPreferredQuality].url);
|
||||
|
||||
this.sources.forEach((source) => {
|
||||
if (source.mimeType.indexOf("audio") > -1) {
|
||||
this.audSrc = source.url;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: detect this.isMusic from the video or channel metadata instead of just SB segments
|
||||
this.$youtube.getSponsorBlock(this.video.id, (data) => {
|
||||
// console.warn("sbreturn", data);
|
||||
if (Array.isArray(data)) {
|
||||
|
@ -439,9 +501,7 @@ export default {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO: detect this.isMusic from the video or channel metadata instead of just SB segments
|
||||
|
||||
this.$refs.player.addEventListener("loadeddata", this.loadedDataEvent);
|
||||
this.aud.addEventListener("loadeddata", this.loadedAudioEvent);
|
||||
},
|
||||
created() {
|
||||
screen.orientation.addEventListener("change", () =>
|
||||
|
@ -452,19 +512,28 @@ export default {
|
|||
this.cleanup();
|
||||
},
|
||||
methods: {
|
||||
loadedAudioEvent() {
|
||||
this.$refs.player.addEventListener("loadeddata", this.loadedDataEvent);
|
||||
this.loadedDataEvent();
|
||||
},
|
||||
loadedDataEvent() {
|
||||
// console.log(e);
|
||||
// if (vid.networkState === vid.NETWORK_LOADING) {
|
||||
// // The user agent is actively trying to download data.
|
||||
// }
|
||||
// networkState: An integer property that represents the network state of the video. The possible values are:
|
||||
// NETWORK_EMPTY (0): No source has been set or the video element's load() method has not been called.
|
||||
// NETWORK_IDLE (1): The video element's load() method has been called, and the video is fetching the media resource.
|
||||
// NETWORK_LOADING (2): The video is in the process of downloading the media resource.
|
||||
// NETWORK_NO_SOURCE (3): No suitable source found for the video.
|
||||
|
||||
// if (vid.readyState < vid.HAVE_FUTURE_DATA) {
|
||||
// // There is not enough data to keep playing from this point
|
||||
// }
|
||||
if (this.vid.readyState >= 3) {
|
||||
this.$refs.audio.play();
|
||||
//readyState: An integer property that represents the readiness state of the video. The possible values are:
|
||||
// HAVE_NOTHING (0): No information about the media resource is available.
|
||||
// HAVE_METADATA (1): Basic metadata about the media resource, such as duration, is available.
|
||||
// HAVE_CURRENT_DATA (2): Data for the current playback position is available but not enough to start playback.
|
||||
// HAVE_FUTURE_DATA (3): Data for the current and at least the next frame is available.
|
||||
// HAVE_ENOUGH_DATA (4): Enough data is available to start playback.
|
||||
|
||||
if (this.vid.readyState >= 3 && this.aud.readyState >= 3) {
|
||||
this.vid.play();
|
||||
this.aud.play();
|
||||
this.bufferingDetected = false;
|
||||
this.$refs.audio.currentTime = this.vid.currentTime;
|
||||
|
||||
if (!this.isMusic) {
|
||||
this.$refs.audio.playbackRate = this.$store.state.player.speed;
|
||||
|
@ -508,7 +577,11 @@ export default {
|
|||
clearTimeout(this.bufferingDetected);
|
||||
this.bufferingDetected = false;
|
||||
}
|
||||
if (this.$refs.audio.paused && !this.$refs.player.paused)
|
||||
if (
|
||||
this.$refs.audio.paused &&
|
||||
!this.$refs.player.paused &&
|
||||
this.$refs.player.readyState >= 3
|
||||
)
|
||||
this.$refs.audio.play();
|
||||
this.buffered = (this.vid.buffered.end(0) / this.vid.duration) * 100;
|
||||
},
|
||||
|
@ -528,14 +601,14 @@ export default {
|
|||
clearTimeout(this.bufferingDetected);
|
||||
this.$refs.audio.currentTime = this.vid.currentTime;
|
||||
this.bufferingDetected = false;
|
||||
this.$refs.audio.play();
|
||||
}
|
||||
this.$refs.audio.play();
|
||||
},
|
||||
cleanup() {
|
||||
if (this.xhr) this.xhr.abort();
|
||||
if (this.isFullscreen) this.exitFullscreen();
|
||||
if (this.bufferingDetected) clearTimeout(this.bufferingDetected);
|
||||
screen.orientation.removeEventListener("change");
|
||||
// screen.orientation.removeEventListener("change");
|
||||
this.$refs.player.removeEventListener("loadeddata", this.loadedDataEvent);
|
||||
this.$refs.player.removeEventListener("timeupdate", this.timeUpdateEvent);
|
||||
this.$refs.player.removeEventListener("progress", this.progressEvent);
|
||||
|
@ -551,9 +624,8 @@ export default {
|
|||
"load",
|
||||
() => {
|
||||
if (this.xhr.status === 200) {
|
||||
var blob = this.xhr.response;
|
||||
console.error(this.xhr);
|
||||
this.blobToDataURL(blob, (dataurl) => {
|
||||
this.blobToDataURL(this.xhr.response, (dataurl) => {
|
||||
console.log(dataurl);
|
||||
this.vidSrc = dataurl;
|
||||
this.buffered = 100;
|
||||
|
@ -579,13 +651,10 @@ export default {
|
|||
|
||||
this.xhr.send();
|
||||
},
|
||||
// !NOTE: (BUG) too big to process for 1080p vids over 2 minutes
|
||||
|
||||
blobToDataURL(blob, callback) {
|
||||
var a = new FileReader();
|
||||
a.onload = function (e) {
|
||||
callback(e.target.result);
|
||||
};
|
||||
a.readAsDataURL(blob);
|
||||
let url = URL.createObjectURL(new Blob([blob]));
|
||||
callback(url);
|
||||
},
|
||||
shortNext() {
|
||||
this.shortTransition = true;
|
||||
|
|
|
@ -18,10 +18,7 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
video: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
video: {},
|
||||
buffering: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
|
@ -38,10 +38,7 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
controls: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
controls: {},
|
||||
buffered: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
|
|
@ -7,7 +7,14 @@
|
|||
scrollable
|
||||
>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn fab text small color="white" v-bind="attrs" v-on="on">
|
||||
<v-btn
|
||||
fab
|
||||
text
|
||||
small
|
||||
color="white"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
{{
|
||||
sources.find((src) => src.url == currentSource.src).qualityLabel
|
||||
? sources.find((src) => src.url == currentSource.src).qualityLabel
|
||||
|
@ -29,7 +36,7 @@
|
|||
</v-subheader>
|
||||
<v-divider />
|
||||
<v-card-text
|
||||
style="max-height: 50vh"
|
||||
style="max-height: 50vh; flex-direction: column !important"
|
||||
class="pa-0 d-flex flex-column-reverse"
|
||||
>
|
||||
<v-list-item
|
||||
|
@ -58,7 +65,7 @@
|
|||
<v-list-item-title>
|
||||
{{ src.qualityLabel ? src.qualityLabel : "" }} ({{
|
||||
src.quality
|
||||
}}) {{ src.bitrate }}bps
|
||||
}}) {{ (src.bitrate / 1000000).toFixed(2) }}Mbps
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
{{ src.mimeType }} {{ src.averageBitrate }}
|
||||
|
@ -72,12 +79,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
currentSource: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
currentSource: {},
|
||||
sources: {
|
||||
type: Array,
|
||||
required: true,
|
||||
|
|
|
@ -58,14 +58,8 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
video: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
controls: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
video: {},
|
||||
controls: {},
|
||||
fullscreen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
|
@ -74,10 +68,7 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
currentTime: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
currentTime: {},
|
||||
duration: {
|
||||
type: Number,
|
||||
required: true,
|
||||
|
|
|
@ -44,10 +44,7 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
controls: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
controls: {},
|
||||
},
|
||||
data: () => ({
|
||||
colors: {
|
||||
|
|
|
@ -12,14 +12,8 @@ export default {
|
|||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
currentTime: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
controls: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
currentTime: {},
|
||||
controls: {},
|
||||
},
|
||||
computed: {
|
||||
humanDuration() {
|
||||
|
|
|
@ -84,10 +84,10 @@ export default {
|
|||
try {
|
||||
const videoId =
|
||||
this.playlist.videos.length === 0 ? "" : this.playlist.videos[0].id;
|
||||
return `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`;
|
||||
return `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`;
|
||||
} catch (e) {
|
||||
alert(e.message);
|
||||
return `https://img.youtube.com/vi//maxresdefault.jpg`;
|
||||
return `https://img.youtube.com/vi//hqdefault.jpg`;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -89,7 +89,7 @@ export default {
|
|||
props: { video: { type: Object, required: true } },
|
||||
computed: {
|
||||
thumbnail() {
|
||||
return `https://img.youtube.com/vi/${this.video.id}/maxresdefault.jpg`;
|
||||
return `https://img.youtube.com/vi/${this.video.id}/hqdefault.jpg`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div class="yt-text-formatter">
|
||||
<p v-html="formatTitle">
|
||||
{{ textRuns.content }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
textRuns: {},
|
||||
additionalInfo: {},
|
||||
},
|
||||
emits: ["play", "pause"],
|
||||
data: () => ({
|
||||
paused: false,
|
||||
}),
|
||||
computed: {
|
||||
formatTitle() {
|
||||
let img =
|
||||
"<img src = 'https://www.gstatic.com/youtube/img/watch/yt_favicon.png' style=\"height: 10px; width: 14px;\"/>";
|
||||
if (this.textRuns.content && this.textRuns.commandRuns) {
|
||||
let tempContent = this.textRuns.content;
|
||||
let arrayWithReplaceParts = [];
|
||||
this.textRuns.commandRuns.forEach((commandRun) => {
|
||||
//[textToReplace, urlFromEndpoint]
|
||||
const textBeforeReplace = this.textRuns.content.substring(
|
||||
commandRun.startIndex,
|
||||
commandRun.startIndex + commandRun.length
|
||||
);
|
||||
// console.log("isExistInArray "+ textBeforeReplace + ": " + isExistInArray);
|
||||
if (commandRun.onTap.innertubeCommand.commandMetadata) {
|
||||
arrayWithReplaceParts.push([
|
||||
textBeforeReplace,
|
||||
commandRun.onTap.innertubeCommand.commandMetadata
|
||||
.webCommandMetadata.url,
|
||||
]);
|
||||
} else if (commandRun.onTap.innertubeCommand.urlEndpoint) {
|
||||
arrayWithReplaceParts.push([
|
||||
this.textRuns.content.substring(
|
||||
commandRun.startIndex,
|
||||
commandRun.startIndex + commandRun.length
|
||||
),
|
||||
commandRun.onTap.innertubeCommand.urlEndpoint.url,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
const duplicates = [];
|
||||
arrayWithReplaceParts = arrayWithReplaceParts.filter((subArr) => {
|
||||
const isFirstElementDuplicate = duplicates.includes(subArr[0]);
|
||||
if (isFirstElementDuplicate) {
|
||||
return false;
|
||||
} else {
|
||||
duplicates.push(subArr[0]);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
console.log(arrayWithReplaceParts);
|
||||
for (let i = 0; i < arrayWithReplaceParts.length; i++) {
|
||||
for (let j = i + 1; j < arrayWithReplaceParts.length; j++) {
|
||||
if (
|
||||
JSON.stringify(arrayWithReplaceParts[i]) ===
|
||||
JSON.stringify(arrayWithReplaceParts[j])
|
||||
) {
|
||||
arrayWithReplaceParts.splice(j, 1);
|
||||
j--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Replacing urls in description
|
||||
arrayWithReplaceParts.forEach((text) => {
|
||||
if (text[1].indexOf("/hashtag/") > -1) {
|
||||
//skip
|
||||
} else if (text[1].indexOf("watch?v=") > -1) {
|
||||
let nameOfUrl = text[0].replace(/ •/, " •");
|
||||
let newUrl =
|
||||
"<a" +
|
||||
' onclick=openInternal("' +
|
||||
text[1] +
|
||||
'") style="background-color: rgba(0,0,0,0.051); border-radius: 8px; white-space: nowrap;">' +
|
||||
img +
|
||||
nameOfUrl +
|
||||
"</a>";
|
||||
tempContent = tempContent.replaceAll(text[0], newUrl);
|
||||
} else if (
|
||||
text[1].indexOf("/channel/") > -1 ||
|
||||
text[1].indexOf("youtube.com/c/") > -1
|
||||
) {
|
||||
let nameOfUrl = text[0].replace(/ /, " ").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
|
||||
let newUrl =
|
||||
"<a" +
|
||||
' onclick=openInternal("channel") style="background-color: rgba(0,0,0,0.051); border-radius: 8px; white-space: nowrap;">' +
|
||||
img +
|
||||
nameOfUrl +
|
||||
"</a>";
|
||||
tempContent = tempContent.replaceAll(
|
||||
new RegExp(`\\b${text[0]}\\b`, "g"),
|
||||
newUrl
|
||||
);
|
||||
} else {
|
||||
let params = new Proxy(new URLSearchParams(text[1]), {
|
||||
get: (searchParams, prop) => searchParams.get(prop),
|
||||
});
|
||||
let url = decodeURI(params.q);
|
||||
let newUrl =
|
||||
"<a" + ' onclick=openExternal("' + url + '")>' + text[0] + "</a>";
|
||||
tempContent = tempContent.replaceAll(text[0], newUrl);
|
||||
}
|
||||
});
|
||||
return tempContent;
|
||||
} else {
|
||||
return this.textRuns.content;
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
window.openExternal = this.openExternal;
|
||||
window.openInternal = this.openInternal;
|
||||
},
|
||||
methods: {
|
||||
openExternal(url) {
|
||||
this.$vuetube.openExternal(url);
|
||||
},
|
||||
async openInternal(url) {
|
||||
await this.$router.push(url);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -1,25 +1,24 @@
|
|||
<template>
|
||||
<div class="description" v-if="render.descriptionBodyText">
|
||||
<yt-text-formatter :textRuns="render.descriptionBodyText.runs">
|
||||
<div v-if="render.attributedDescriptionBodyText.content" class="description">
|
||||
<yt-text-formatter :text-runs="render.attributedDescriptionBodyText">
|
||||
</yt-text-formatter>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import YtTextFormatter from "~/components/UtilRenderers/YtTextFormatterNew.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
YtTextFormatter,
|
||||
},
|
||||
props: ["render"],
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.description {
|
||||
white-space: pre-line;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import YtTextFormatter from "~/components/UtilRenderers/YtTextFormatter.vue";
|
||||
|
||||
export default {
|
||||
props: ["render"],
|
||||
|
||||
components: {
|
||||
YtTextFormatter,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -30,12 +30,20 @@ export default {
|
|||
|
||||
computed: {
|
||||
thumbnailOverlayText() {
|
||||
return this.video.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer
|
||||
.text.runs[0].text;
|
||||
this.video.thumbnailOverlays.forEach((thumbnail) => {
|
||||
if (thumbnail.thumbnailOverlayTimeStatusRenderer) {
|
||||
return thumbnail.thumbnailOverlayTimeStatusRenderer.text.runs[0].text;
|
||||
}
|
||||
});
|
||||
return "";
|
||||
},
|
||||
thumbnailOverlayStyle() {
|
||||
return this.video.thumbnailOverlays[0].thumbnailOverlayTimeStatusRenderer
|
||||
.style;
|
||||
this.video.thumbnailOverlays.forEach((thumbnail) => {
|
||||
if (thumbnail.thumbnailOverlayTimeStatusRenderer) {
|
||||
return thumbnail.thumbnailOverlayTimeStatusRenderer.style;
|
||||
}
|
||||
});
|
||||
return "DEFAULT";
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ export default {
|
|||
props: ["video"],
|
||||
computed: {
|
||||
thumbnail() {
|
||||
return `https://img.youtube.com/vi/${this.video.id}/maxresdefault.jpg`;
|
||||
return `https://img.youtube.com/vi/${this.video.id}/hqdefault.jpg`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -98,4 +98,4 @@ export default {
|
|||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -11,24 +11,24 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@capacitor/splash-screen": "^1.2.2",
|
||||
"@capacitor/status-bar": "^1.0.8",
|
||||
"@capacitor/status-bar": "^5.0.2",
|
||||
"core-js": "^3.25.0",
|
||||
"nuxt": "^2.15.8",
|
||||
"nuxt": "^3.5.2",
|
||||
"uuid": "^9.0.0",
|
||||
"vue": "^2.7.10",
|
||||
"vue-server-renderer": "^2.7.10",
|
||||
"vue-template-compiler": "^2.7.10",
|
||||
"vuetify": "^2.6.9",
|
||||
"webpack": "^4.46.0"
|
||||
"webpack": "^5.84.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/vuetify": "^1.12.3",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"prettier": "^2.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,8 +68,15 @@ export default {
|
|||
.then((result) => {
|
||||
if (result) this.recommends = [result];
|
||||
})
|
||||
.catch(error => {});
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
a {
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -55,13 +55,19 @@
|
|||
>
|
||||
<div>
|
||||
{{ lang.published }}:
|
||||
{{ new Date(update.created_at).toLocaleString() }}
|
||||
{{ new Date(latestVersion.assets[0].created_at).toLocaleString() }}
|
||||
</div>
|
||||
<div>
|
||||
{{ lang.size }}:
|
||||
{{ require("~/plugins/utils").humanFileSize(update.size) }}
|
||||
{{
|
||||
require("~/plugins/utils").humanFileSize(
|
||||
latestVersion.assets[0].size
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
<div>
|
||||
{{ lang.users }}: {{ latestVersion.assets[0].download_count }}
|
||||
</div>
|
||||
<div>{{ lang.users }}: {{ update.download_count }}</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
|
@ -123,7 +129,9 @@ export default {
|
|||
latestVersion: "",
|
||||
lang: {},
|
||||
status: "checking",
|
||||
update: {},
|
||||
update: {
|
||||
created_at: "",
|
||||
},
|
||||
downloading: false,
|
||||
};
|
||||
},
|
||||
|
@ -185,7 +193,7 @@ export default {
|
|||
|
||||
async install() {
|
||||
this.downloading = true;
|
||||
await this.$update(this.update.browser_download_url).catch(() => {
|
||||
await this.$update(this.latestVersion.assets[0].url).catch(() => {
|
||||
this.downloading = false;
|
||||
});
|
||||
//window.open(this.update.browser_download_url, '_blank');
|
||||
|
|
|
@ -40,6 +40,19 @@ module.exports = {
|
|||
return headers;
|
||||
},
|
||||
|
||||
INNERTUBE_NEW_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": "2",
|
||||
"x-youtube-client-version": "2.20230502.01.00",
|
||||
};
|
||||
return headers;
|
||||
},
|
||||
INNERTUBE_CLIENT: (info) => {
|
||||
let client = {
|
||||
gl: info.gl,
|
||||
|
@ -59,4 +72,43 @@ module.exports = {
|
|||
};
|
||||
return client;
|
||||
},
|
||||
INNERTUBE_VIDEO: (info) => {
|
||||
let client = {
|
||||
gl: info.gl,
|
||||
hl: info.hl,
|
||||
deviceMake: info.deviceMake,
|
||||
deviceModel: info.deviceModel,
|
||||
userAgent: info.userAgent,
|
||||
clientName: "MWEB",
|
||||
clientVersion: "2.20230502.01.00",
|
||||
osName: info.osName,
|
||||
osVersion: info.osVersion,
|
||||
platform: "MOBILE",
|
||||
playerType: "UNIPLAYER",
|
||||
screenPixelDensity: "3",
|
||||
originalUrl: info.originalUrl,
|
||||
configInfo: info.configInfo,
|
||||
remoteHost: info.remoteHost,
|
||||
visitorData: info.visitorData,
|
||||
clientFormFactor: "SMALL_FORM_FACTOR",
|
||||
screenDensityFloat: "1",
|
||||
timeZone: info.timeZone,
|
||||
browserName: info.browserName,
|
||||
browserVersion: info.browserVersion,
|
||||
acceptHeader:
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
||||
deviceExperimentId: info.deviceExperimentId,
|
||||
screenWidthPoints: info.screenWidthPoints,
|
||||
screenHeightPoints: info.screenHeightPoints,
|
||||
utcOffsetMinutes: info.utcOffsetMinutes,
|
||||
userInterfaceTheme: "USER_INTERFACE_THEME_LIGHT",
|
||||
memoryTotalKbytes: "8000000",
|
||||
clientScreen: "WATCH",
|
||||
mainAppWebInfo: {
|
||||
webDisplayMode: "WEB_DISPLAY_MODE_BROWSER",
|
||||
isWebNativeShareAvailable: true,
|
||||
},
|
||||
};
|
||||
return client;
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { Http } from "@capacitor-community/http";
|
||||
import { getBetweenStrings, delay } from "./utils";
|
||||
import rendererUtils from "./renderers";
|
||||
import constants from "./constants";
|
||||
import constants, { YT_API_VALUES } from "./constants";
|
||||
|
||||
class Innertube {
|
||||
//--- Initiation ---//
|
||||
|
@ -16,23 +16,122 @@ class Innertube {
|
|||
constructor(ErrorCallback) {
|
||||
this.ErrorCallback = ErrorCallback || undefined;
|
||||
this.retry_count = 0;
|
||||
this.playerParams = "";
|
||||
this.signatureTimestamp = 0;
|
||||
}
|
||||
|
||||
checkErrorCallback() {
|
||||
return typeof this.ErrorCallback === "function";
|
||||
}
|
||||
|
||||
async makeDecipherFunction(html) {
|
||||
// Get url of base.js file
|
||||
const baseJsUrl =
|
||||
constants.URLS.YT_URL +
|
||||
getBetweenStrings(html.data, '"jsUrl":"', '","cssUrl"');
|
||||
// Get base.js content
|
||||
const baseJs = await Http.get({
|
||||
url: baseJsUrl,
|
||||
}).catch((error) => error);
|
||||
// Example:
|
||||
//;var IF={k4:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c},
|
||||
// VN:function(a){a.reverse()},
|
||||
// DW:function(a,b){a.splice(0,b)}};
|
||||
let isMatch;
|
||||
if (
|
||||
/;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z0-9]+:function\(a\)\{[^}]*\},\n[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\}\};/.exec(
|
||||
baseJs.data
|
||||
)
|
||||
) {
|
||||
isMatch =
|
||||
/;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z0-9]+:function\(a\)\{[^}]*\},\n[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\}\};/.exec(
|
||||
baseJs.data
|
||||
);
|
||||
} else if (
|
||||
/;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z0-9]+:function\([A-Za-z],[A-Za-z]\)\{[^}]*\},\n[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\}\};/.exec(
|
||||
baseJs.data
|
||||
)
|
||||
) {
|
||||
isMatch =
|
||||
/;var [A-Za-z]+=\{[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\},\n[A-Za-z0-9]+:function\([A-Za-z],[A-Za-z]\)\{[^}]*\},\n[A-Za-z0-9]+:function\([^)]*\)\{[^}]*\}\};/.exec(
|
||||
baseJs.data
|
||||
);
|
||||
}
|
||||
|
||||
if (isMatch) {
|
||||
console.log("The input string matches the regex pattern.");
|
||||
const firstPart = isMatch[0].substring(1);
|
||||
|
||||
if (
|
||||
/\{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return [A-Za-z]\.join\(""\)\};/.exec(
|
||||
baseJs.data
|
||||
)
|
||||
) {
|
||||
isMatch =
|
||||
/\{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return [A-Za-z]\.join\(""\)\};/.exec(
|
||||
baseJs.data
|
||||
);
|
||||
} else if (
|
||||
/{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return +[A-Za-z]\.join\(""\)};/.exec(
|
||||
baseJs.data
|
||||
)
|
||||
) {
|
||||
isMatch =
|
||||
/{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return +[A-Za-z]\.join\(""\)};/.exec(
|
||||
baseJs.data
|
||||
);
|
||||
} else if (
|
||||
/\{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return +[A-Za-z]\.join\(""\)};/.exec(
|
||||
baseJs.data
|
||||
)
|
||||
) {
|
||||
isMatch =
|
||||
/\{[A-Za-z]=[A-Za-z]\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return +[A-Za-z]\.join\(""\)};/.exec(
|
||||
baseJs.data
|
||||
);
|
||||
} else {
|
||||
isMatch =
|
||||
/\{a=a\.split\(""\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);[A-Za-z]+\.[A-Za-z0-9]+\([^)]*\);return a\.join\(""\)\};/.exec(
|
||||
baseJs.data
|
||||
);
|
||||
}
|
||||
if (!isMatch) {
|
||||
console.warn(
|
||||
"The second part of decipher string does not match the regex pattern."
|
||||
);
|
||||
}
|
||||
// Example:
|
||||
// {a=a.split("");IF.k4(a,4);IF.VN(a,68);IF.DW(a,2);IF.VN(a,66);IF.k4(a,19);IF.DW(a,2);IF.VN(a,36);IF.DW(a,2);IF.k4(a,41);return a.join("")};
|
||||
|
||||
// Get second part of decipher function
|
||||
const secondPart =
|
||||
"var decodeUrl=function(a)" + isMatch[0] + "return decodeUrl;";
|
||||
let decodeFunction = firstPart + secondPart;
|
||||
let decodeUrlFunction = new Function(decodeFunction);
|
||||
this.decodeUrl = decodeUrlFunction();
|
||||
let signatureIntValue = /.sts="[0-9]+";/.exec(baseJs.data);
|
||||
// Get signature timestamp
|
||||
this.signatureTimestamp = signatureIntValue[0].replace(/\D/g, "");
|
||||
} else {
|
||||
console.warn(
|
||||
"The first part of decipher string does not match the regex pattern."
|
||||
);
|
||||
}
|
||||
}
|
||||
async initAsync() {
|
||||
const html = await Http.get({
|
||||
url: constants.URLS.YT_URL,
|
||||
params: { hl: "en" },
|
||||
}).catch((error) => error);
|
||||
|
||||
await this.makeDecipherFunction(html);
|
||||
try {
|
||||
if (html instanceof Error && this.checkErrorCallback)
|
||||
this.ErrorCallback(html.message, true);
|
||||
|
||||
try {
|
||||
const data = JSON.parse(
|
||||
getBetweenStrings(html.data, "ytcfg.set(", ");")
|
||||
"{" + getBetweenStrings(html.data, "ytcfg.set({", ");")
|
||||
);
|
||||
if (data.INNERTUBE_CONTEXT) {
|
||||
this.key = data.INNERTUBE_API_KEY;
|
||||
|
@ -81,7 +180,11 @@ class Innertube {
|
|||
//--- API Calls ---//
|
||||
|
||||
async browseAsync(action_type, args = {}) {
|
||||
let data = { context: this.context };
|
||||
let data = {
|
||||
context: {
|
||||
client: constants.INNERTUBE_CLIENT(this.context.client),
|
||||
},
|
||||
};
|
||||
|
||||
switch (action_type) {
|
||||
case "recommendations":
|
||||
|
@ -160,7 +263,12 @@ class Innertube {
|
|||
}
|
||||
|
||||
async getVidAsync(id) {
|
||||
let data = { context: this.context, videoId: id };
|
||||
let data = {
|
||||
context: {
|
||||
client: constants.INNERTUBE_VIDEO(this.context.client),
|
||||
},
|
||||
videoId: id,
|
||||
};
|
||||
const responseNext = await Http.post({
|
||||
url: `${constants.URLS.YT_BASE_API}/next?key=${this.key}`,
|
||||
data: {
|
||||
|
@ -179,8 +287,34 @@ class Innertube {
|
|||
|
||||
const response = await Http.post({
|
||||
url: `${constants.URLS.YT_BASE_API}/player?key=${this.key}`,
|
||||
data: data,
|
||||
headers: constants.INNERTUBE_HEADER(this.context.client),
|
||||
data: {
|
||||
...data,
|
||||
...{
|
||||
playerParams: this.playerParams,
|
||||
contentCheckOk: false,
|
||||
mwebCapabilities: {
|
||||
mobileClientSupportsLivestream: true,
|
||||
},
|
||||
playbackContext: {
|
||||
contentPlaybackContext: {
|
||||
currentUrl: "/watch?v=" + id + "&pp=" + this.playerParams,
|
||||
vis: 0,
|
||||
splay: false,
|
||||
autoCaptionsDefaultOn: false,
|
||||
autonavState: "STATE_NONE",
|
||||
html5Preference: "HTML5_PREF_WANTS",
|
||||
signatureTimestamp: this.signatureTimestamp,
|
||||
referer: "https://m.youtube.com/",
|
||||
lactMilliseconds: "-1",
|
||||
watchAmbientModeContext: {
|
||||
watchAmbientModeEnabled: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// headers: constants.INNERTUBE_HEADER(this.context.client),
|
||||
headers: constants.INNERTUBE_NEW_HEADER(this.context.client),
|
||||
}).catch((error) => error);
|
||||
|
||||
if (response.error)
|
||||
|
@ -270,7 +404,7 @@ class Innertube {
|
|||
|
||||
static getThumbnail(id, resolution) {
|
||||
if (resolution == "max") {
|
||||
const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg`;
|
||||
const url = `https://img.youtube.com/vi/${id}/hqdefault.jpg`;
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
|
@ -319,6 +453,8 @@ class Innertube {
|
|||
const responseInfo = response.data.output;
|
||||
const responseNext = response.data.outputNext;
|
||||
const details = responseInfo.videoDetails;
|
||||
const publishDate =
|
||||
responseInfo.microformat.playerMicroformatRenderer.publishDate;
|
||||
// const columnUI =
|
||||
// responseInfo[3].response?.contents.singleColumnWatchNextResults?.results
|
||||
// ?.results;
|
||||
|
@ -337,6 +473,33 @@ class Innertube {
|
|||
(content) => content.slimOwnerRenderer
|
||||
)?.slimOwnerRenderer;
|
||||
|
||||
try {
|
||||
console.log(vidMetadata.contents);
|
||||
this.playerParams =
|
||||
ownerData.navigationEndpoint.watchEndpoint.playerParams;
|
||||
} catch (e) {}
|
||||
// Deciphering urls
|
||||
resolutions.formats
|
||||
.concat(resolutions.adaptiveFormats)
|
||||
.forEach((source) => {
|
||||
if (source.signatureCipher) {
|
||||
const params = new Proxy(
|
||||
new URLSearchParams(source.signatureCipher),
|
||||
{
|
||||
get: (searchParams, prop) => searchParams.get(prop),
|
||||
}
|
||||
);
|
||||
if (params.s) {
|
||||
let cipher = decodeURIComponent(params.s);
|
||||
let decipheredValue = this.decodeUrl(cipher);
|
||||
// console.log("decipheredValue", decipheredValue);
|
||||
source["url"] = (params.url + "&sig=" + decipheredValue).replace(
|
||||
/&/g,
|
||||
"&"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
const vidData = {
|
||||
id: details.videoId,
|
||||
title: details.title,
|
||||
|
@ -350,6 +513,7 @@ class Innertube {
|
|||
availableResolutions: resolutions?.formats,
|
||||
availableResolutionsAdaptive: resolutions?.adaptiveFormats,
|
||||
metadata: {
|
||||
publishDate: publishDate,
|
||||
contents: vidMetadata.contents,
|
||||
description: details.shortDescription,
|
||||
thumbnails: details.thumbnails?.thumbnails,
|
||||
|
|
|
@ -7,7 +7,7 @@ module.exports = {
|
|||
library: "Knihovna",
|
||||
restart: "Restartovat",
|
||||
later: "Později",
|
||||
settingRestart: "Úprava tohoto nastavení vyžaduje restart aplikace pro provedení změn."
|
||||
settingRestart: "Úprava tohoto nastavení vyžaduje restartování aplikace pro provedení změn."
|
||||
},
|
||||
|
||||
index: {
|
||||
|
@ -31,7 +31,7 @@ module.exports = {
|
|||
|
||||
mods: {
|
||||
general: {
|
||||
language: "Jatyk",
|
||||
language: "Jazyk",
|
||||
},
|
||||
theme: {
|
||||
normal: "Normální",
|
||||
|
@ -63,7 +63,7 @@ module.exports = {
|
|||
more: "Více",
|
||||
},
|
||||
about: {
|
||||
appinformation: "Informace o aplikace",
|
||||
appinformation: "Informace o aplikaci",
|
||||
appversion: "Verze aplikace",
|
||||
deviceinformation: "Informace o zařízení",
|
||||
platform: "Platforma",
|
||||
|
|
|
@ -55,7 +55,9 @@ module.exports = {
|
|||
language: "言語",
|
||||
backup: "バックアップ",
|
||||
backupinfo: "アプリの設定をバックアップ、または復元する。",
|
||||
restore: "復元"
|
||||
restore: "復元",
|
||||
personalizedrecommendations: "Personalized recommendations",
|
||||
personalizedrecommendationsinfo: "Receive personalized recommendations in exchange for sending watch time telemetry.",
|
||||
},
|
||||
theme: {
|
||||
normal: "通常",
|
||||
|
@ -73,6 +75,17 @@ module.exports = {
|
|||
roundthumbnails: "丸みを帯びたサムネイル",
|
||||
roundwatchpagecomponents: "視聴画面のコンポーネントを丸く",
|
||||
radius: "半径",
|
||||
launchscreen: "起動画面",
|
||||
centeredlayout: "中央揃えのレイアウト",
|
||||
fullscreenlayout: "全画面レイアウト",
|
||||
themedicon: "テーマアイコン",
|
||||
bottomnavigation: "下部のナビゲーション",
|
||||
shift: "シフト",
|
||||
showlabels: "ラベルを表示する",
|
||||
mdi: "MDI",
|
||||
materialsymbols: "マテリアルシンボル",
|
||||
fluentuiicons: "FluentUI アイコン",
|
||||
ibmcarbonicons: "IBM カーボン アイコン",
|
||||
},
|
||||
startup: {
|
||||
defaultpage: "起動時のページ",
|
||||
|
|
|
@ -1,93 +1,121 @@
|
|||
module.exports = {
|
||||
name: "Українська",
|
||||
|
||||
global: {
|
||||
home: "Головна",
|
||||
subscriptions: "Підписки",
|
||||
library: "Бібліотека",
|
||||
restart: "Перезапустити",
|
||||
later: "Пізніше",
|
||||
settingRestart: "Зміна цього параметра вимагає перезапуску"
|
||||
},
|
||||
|
||||
index: {
|
||||
connecting: "Підключення",
|
||||
plugins: "Завантаження плагінів",
|
||||
launching: "Запуск",
|
||||
},
|
||||
|
||||
settings: {
|
||||
general: "Загальні",
|
||||
theme: "Тема",
|
||||
player: "Відеоплеєр",
|
||||
uitweaker: "Налаштування UI",
|
||||
startupoptions: "Параметри запуску",
|
||||
plugins: "Плагіни",
|
||||
updates: "Оновлення",
|
||||
logs: "Журнали",
|
||||
about: "Про додаток",
|
||||
devmode: "Редактор реєстру",
|
||||
},
|
||||
|
||||
mods: {
|
||||
general: {
|
||||
language: "Мова",
|
||||
},
|
||||
theme: {
|
||||
normal: "Звичайна",
|
||||
adaptive: "Адаптивна",
|
||||
custom: "Власна",
|
||||
dark: "Темна",
|
||||
black: "Чорна",
|
||||
darkmode: "Темний режим",
|
||||
darkmodetagline: "Bravo Six, Going Dark.",
|
||||
},
|
||||
tweaks: {
|
||||
fullscreen: "Повноекранний режим",
|
||||
navbarblur: "Розмиття навігаційної панелі",
|
||||
roundedcorners: "Закруглені кути",
|
||||
roundthumbnails: "Закруглені мініатюри",
|
||||
roundwatchpagecomponents: "Закруглені компоненти сторінки перегляду",
|
||||
radius: "Радіус",
|
||||
},
|
||||
startup: {
|
||||
defaultpage: "Домашня сторінка",
|
||||
},
|
||||
updates: {
|
||||
install: "Встановити",
|
||||
view: "Переглянути",
|
||||
latest: "Остання",
|
||||
installed: "Встановлена",
|
||||
},
|
||||
logs: {
|
||||
more: "Більше",
|
||||
},
|
||||
about: {
|
||||
appinformation: "Інформація про додаток",
|
||||
appversion: "Версія додатку",
|
||||
deviceinformation: "Інформація про пристрій",
|
||||
platform: "Платформа",
|
||||
os: "Операційна система",
|
||||
model: "Модель",
|
||||
manufacturer: "Виробник",
|
||||
emulator: "Емулятор",
|
||||
github: "GitHub",
|
||||
discord: "Discord",
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
welcome: "Ласкаво просимо до VueTube",
|
||||
tagline: "Майбутнє відео-стрімінгу",
|
||||
next: "Далі",
|
||||
updated: "VueTube оновлено!",
|
||||
awesome: "Добре!",
|
||||
langsetup: "Давайте виберемо мову!",
|
||||
featuresetup: "Давайте виберемо деякі функції!",
|
||||
enableryd: "Увімкнути Return YouTube Dislike",
|
||||
enablespb: "Увімкнути SponsorBlock",
|
||||
thanks: "Дякуємо за використання VueTube",
|
||||
enjoy: "Ми сподіваємося, що вам сподобається!",
|
||||
packageinstaller: "Виберіть пакет для завантаження"
|
||||
},
|
||||
};
|
||||
module.exports = {
|
||||
name: "Українська",
|
||||
|
||||
global: {
|
||||
home: "Головна",
|
||||
subscriptions: "Підписки",
|
||||
library: "Бібліотека",
|
||||
restart: "Перезапустити",
|
||||
later: "Пізніше",
|
||||
settingRestart: "Зміна цього параметра вимагає перезапуску"
|
||||
},
|
||||
|
||||
index: {
|
||||
connecting: "Підключення",
|
||||
plugins: "Завантаження плагінів",
|
||||
launching: "Запуск",
|
||||
},
|
||||
|
||||
settings: {
|
||||
general: "Загальні",
|
||||
theme: "Тема",
|
||||
player: "Відеоплеєр",
|
||||
uitweaker: "Налаштування UI",
|
||||
startupoptions: "Параметри запуску",
|
||||
plugins: "Плагіни",
|
||||
updates: "Оновлення",
|
||||
logs: "Журнали",
|
||||
about: "Про додаток",
|
||||
devmode: "Редактор реєстру",
|
||||
},
|
||||
|
||||
mods: {
|
||||
general: {
|
||||
language: "Мова",
|
||||
backup: "Backup",
|
||||
backupinfo: "Backup or restore your application settings",
|
||||
restore: "Restore",
|
||||
personalizedrecommendations: "Персоналізовані рекомендації",
|
||||
personalizedrecommendationsinfo:
|
||||
"Отримуйте персоналізовані рекомендації в обмін на надсилання телеметричних даних про час перегляду.",
|
||||
},
|
||||
developer: {
|
||||
registryeditor: "Registry editor",
|
||||
registrywarning: "CHANGING ENTRIES MAY CAUSE YOUR APP TO BREAK!",
|
||||
createentry: "Create entry",
|
||||
createentryfull: "Create registry entry",
|
||||
cancel: "Cancel",
|
||||
create: "Create",
|
||||
key: "Key",
|
||||
value: "Value",
|
||||
confirmdelete: "Confirm delete",
|
||||
areyousure: "Are you sure that you want to delete?",
|
||||
delete: "Delete",
|
||||
change: "Change",
|
||||
},
|
||||
theme: {
|
||||
normal: "Звичайна",
|
||||
adaptive: "Адаптивна",
|
||||
custom: "Власна",
|
||||
dark: "Темна",
|
||||
black: "Чорна",
|
||||
darkmode: "Темний режим",
|
||||
darkmodetagline: "Bravo Six, Going Dark.",
|
||||
},
|
||||
tweaks: {
|
||||
fullscreen: "Повноекранний режим",
|
||||
navbarblur: "Розмиття навігаційної панелі",
|
||||
roundedcorners: "Закруглені кути",
|
||||
roundthumbnails: "Закруглені мініатюри",
|
||||
roundwatchpagecomponents: "Закруглені компоненти сторінки перегляду",
|
||||
radius: "Радіус",
|
||||
},
|
||||
startup: {
|
||||
defaultpage: "Домашня сторінка",
|
||||
},
|
||||
updates: {
|
||||
install: "Встановити",
|
||||
view: "Переглянути",
|
||||
latest: "Остання",
|
||||
installed: "Встановлена",
|
||||
size: "Розмір",
|
||||
users: "Завантажень",
|
||||
published: "Опубліковано",
|
||||
|
||||
okay: "ОК",
|
||||
refresh: "Оновити",
|
||||
update: "Завантажити",
|
||||
later: "Пізніше",
|
||||
},
|
||||
logs: {
|
||||
more: "Більше",
|
||||
},
|
||||
about: {
|
||||
appinformation: "Інформація про додаток",
|
||||
appversion: "Версія додатку",
|
||||
deviceinformation: "Інформація про пристрій",
|
||||
platform: "Платформа",
|
||||
os: "Операційна система",
|
||||
model: "Модель",
|
||||
manufacturer: "Виробник",
|
||||
emulator: "Емулятор",
|
||||
github: "GitHub",
|
||||
discord: "Discord",
|
||||
},
|
||||
},
|
||||
|
||||
events: {
|
||||
welcome: "Ласкаво просимо до VueTube",
|
||||
tagline: "Майбутнє відео-стрімінгу",
|
||||
next: "Далі",
|
||||
updated: "VueTube оновлено!",
|
||||
awesome: "Добре!",
|
||||
langsetup: "Давайте виберемо мову!",
|
||||
featuresetup: "Давайте виберемо деякі функції!",
|
||||
enableryd: "Увімкнути Return YouTube Dislike",
|
||||
enablespb: "Увімкнути SponsorBlock",
|
||||
thanks: "Дякуємо за використання VueTube",
|
||||
enjoy: "Ми сподіваємося, що вам сподобається!",
|
||||
packageinstaller: "Виберіть пакет для завантаження"
|
||||
},
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ class rendererUtils {
|
|||
} else if (base.watchEndpoint) {
|
||||
return `/watch?v=${base.watchEndpoint.videoId}`;
|
||||
} else if (base.navigationEndpoint) {
|
||||
return; //for now
|
||||
return base.navigationEndpoint.browseEndpoint.canonicalBaseUrl; //for now
|
||||
} else if (base.searchEndpoint) {
|
||||
return `/search?q=${encodeURI(base.searchEndpoint.query)}`;
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ const innertubeModule = {
|
|||
|
||||
getThumbnail(id, resolution, backupThumbnail) {
|
||||
if (resolution == "max") {
|
||||
const url = `https://img.youtube.com/vi/${id}/maxresdefault.jpg`;
|
||||
const url = `https://img.youtube.com/vi/${id}/hqdefault.jpg`;
|
||||
let img = new Image();
|
||||
img.src = url;
|
||||
img.onload = function () {
|
||||
|
|
|
@ -6,7 +6,7 @@ const getDefaultState = () => {
|
|||
banner: null,
|
||||
title: null,
|
||||
subscribe: null,
|
||||
subscribeAlt: null,
|
||||
subscribeAlt: "",
|
||||
descriptionPreview: null,
|
||||
subscribers: null,
|
||||
videosCount: null,
|
||||
|
@ -22,12 +22,18 @@ export const actions = {
|
|||
Object.assign(state, getDefaultState());
|
||||
state.loading = true;
|
||||
console.log(channelUrl);
|
||||
const channelRequest =
|
||||
let channelRequest = "";
|
||||
console.warn(channelUrl);
|
||||
if (
|
||||
channelUrl.includes("/c/") ||
|
||||
channelUrl.includes("/user/") ||
|
||||
channelUrl.includes("/channel/")
|
||||
? `https://youtube.com/${channelUrl}`
|
||||
: `https://youtube.com/channel/${channelUrl}`;
|
||||
channelUrl.includes("/channel/") ||
|
||||
channelUrl.includes("/@")
|
||||
) {
|
||||
channelRequest = `https://youtube.com/${channelUrl}`;
|
||||
} else {
|
||||
channelRequest = `https://youtube.com/channel/${channelUrl}`;
|
||||
}
|
||||
this.$youtube
|
||||
.getChannel(channelRequest)
|
||||
.then((channel) => {
|
||||
|
|
|
@ -14,7 +14,10 @@ export const state = () => ({
|
|||
export const mutations = {
|
||||
initPlayer(state) {
|
||||
if (process.client) {
|
||||
state.loop = JSON.parse(localStorage.getItem("loop")) === true; // defaults to false
|
||||
state.loop =
|
||||
localStorage.getItem("loop") !== "undefined"
|
||||
? JSON.parse(localStorage.getItem("loop"))
|
||||
: true; // defaults to false
|
||||
state.speed = JSON.parse(localStorage.getItem("speed")) || 1; // defaults to 1
|
||||
state.speedAutosave = !(
|
||||
// false if false, defaults to true
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"@capacitor/android": "^3.7.0",
|
||||
"@capacitor/app": "^1.1.1",
|
||||
"@capacitor/cli": "^5.0.4",
|
||||
"@capacitor/core": "^3.7.0",
|
||||
"@capacitor/core": "^5.0.4",
|
||||
"@capacitor/device": "^1.1.2",
|
||||
"@capacitor/filesystem": "^1.1.0",
|
||||
"@capacitor/haptics": "^1.1.4",
|
||||
|
|
Loading…
Reference in New Issue