Invidious: Randomize instance on startup

There are now two separate settings related to Invidious instances:
- currentInvidiousInstance
- defaultInvidiousInstance

'currentInvidiousInstance' is a value that exists solely in memory
and it's the value used by the app to make the API calls,
while 'defaultInvidiousInstance' is the value that can be persisted
in the database by user action and will be used to initiate
the 'currentInvidiousInstance' on startup.

If the user has not saved a default value to the database,
'currentInvidiousInstance' is randomized from
a fetched list of viable candidates.
This commit is contained in:
Svallinn 2021-07-03 02:55:56 +01:00
parent d55be8dcc5
commit 40d7278383
No known key found for this signature in database
GPG Key ID: 09FB527F34037CCA
24 changed files with 234 additions and 120 deletions

View File

@ -1,5 +1,5 @@
import Vue from 'vue' import Vue from 'vue'
import { mapActions } from 'vuex' import { mapActions, mapMutations } from 'vuex'
import { ObserveVisibility } from 'vue-observe-visibility' import { ObserveVisibility } from 'vue-observe-visibility'
import FtFlexBox from './components/ft-flex-box/ft-flex-box.vue' import FtFlexBox from './components/ft-flex-box/ft-flex-box.vue'
import TopNav from './components/top-nav/top-nav.vue' import TopNav from './components/top-nav/top-nav.vue'
@ -79,10 +79,18 @@ export default Vue.extend({
}, },
externalPlayer: function () { externalPlayer: function () {
return this.$store.getters.getExternalPlayer return this.$store.getters.getExternalPlayer
},
defaultInvidiousInstance: function () {
return this.$store.getters.getDefaultInvidiousInstance
} }
}, },
mounted: function () { mounted: function () {
this.grabUserSettings().then(() => { this.grabUserSettings().then(async () => {
await this.fetchInvidiousInstances()
if (this.defaultInvidiousInstance === '') {
await this.setRandomCurrentInvidiousInstance()
}
this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => { this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => {
this.grabHistory() this.grabHistory()
this.grabAllPlaylists() this.grabAllPlaylists()
@ -378,6 +386,10 @@ export default Vue.extend({
} }
}, },
...mapMutations([
'setInvidiousInstancesList'
]),
...mapActions([ ...mapActions([
'showToast', 'showToast',
'openExternalLink', 'openExternalLink',
@ -387,6 +399,8 @@ export default Vue.extend({
'grabAllPlaylists', 'grabAllPlaylists',
'getYoutubeUrlInfo', 'getYoutubeUrlInfo',
'getExternalPlayerCmdArgumentsData', 'getExternalPlayerCmdArgumentsData',
'fetchInvidiousInstances',
'setRandomCurrentInvidiousInstance',
'setupListenerToSyncWindows' 'setupListenerToSyncWindows'
]) ])
} }

View File

@ -46,8 +46,8 @@ export default Vue.extend({
backendFallback: function () { backendFallback: function () {
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
profileList: function () { profileList: function () {
return this.$store.getters.getProfileList return this.$store.getters.getProfileList

View File

@ -24,8 +24,8 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
listType: function () { listType: function () {
return this.$store.getters.getListType return this.$store.getters.getListType
@ -66,7 +66,7 @@ export default Vue.extend({
}, },
parseInvidiousData: function () { parseInvidiousData: function () {
this.thumbnail = this.data.authorThumbnails[2].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) this.thumbnail = this.data.authorThumbnails[2].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
this.channelName = this.data.author this.channelName = this.data.author
this.id = this.data.authorId this.id = this.data.authorId
if (this.hideChannelSubscriptions) { if (this.hideChannelSubscriptions) {

View File

@ -29,8 +29,8 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
listType: function () { listType: function () {
@ -79,7 +79,7 @@ export default Vue.extend({
parseInvidiousData: function () { parseInvidiousData: function () {
this.title = this.data.title this.title = this.data.title
this.thumbnail = this.data.playlistThumbnail.replace('https://i.ytimg.com', this.invidiousInstance).replace('hqdefault', 'mqdefault') this.thumbnail = this.data.playlistThumbnail.replace('https://i.ytimg.com', this.currentInvidiousInstance).replace('hqdefault', 'mqdefault')
this.channelName = this.data.author this.channelName = this.data.author
this.channelLink = this.data.authorUrl this.channelLink = this.data.authorUrl
this.playlistLink = this.data.playlistId this.playlistLink = this.data.playlistId

View File

@ -92,8 +92,8 @@ export default Vue.extend({
return this.$store.getters.getBackendPreference return this.$store.getters.getBackendPreference
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
inHistory: function () { inHistory: function () {
@ -103,11 +103,11 @@ export default Vue.extend({
}, },
invidiousUrl: function () { invidiousUrl: function () {
return `${this.invidiousInstance}/watch?v=${this.id}` return `${this.currentInvidiousInstance}/watch?v=${this.id}`
}, },
invidiousChannelUrl: function () { invidiousChannelUrl: function () {
return `${this.invidiousInstance}/channel/${this.channelId}` return `${this.currentInvidiousInstance}/channel/${this.channelId}`
}, },
youtubeUrl: function () { youtubeUrl: function () {
@ -156,7 +156,7 @@ export default Vue.extend({
thumbnail: function () { thumbnail: function () {
let baseUrl let baseUrl
if (this.backendPreference === 'invidious') { if (this.backendPreference === 'invidious') {
baseUrl = this.invidiousInstance baseUrl = this.currentInvidiousInstance
} else { } else {
baseUrl = 'https://i.ytimg.com' baseUrl = 'https://i.ytimg.com'
} }

View File

@ -42,8 +42,8 @@ export default Vue.extend({
backendPreference: function () { backendPreference: function () {
return this.$store.getters.getBackendPreference return this.$store.getters.getBackendPreference
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
profileList: function () { profileList: function () {
return this.$store.getters.getProfileList return this.$store.getters.getProfileList
@ -80,7 +80,7 @@ export default Vue.extend({
return 0 return 0
}).map((channel) => { }).map((channel) => {
if (this.backendPreference === 'invidious') { if (this.backendPreference === 'invidious') {
channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
} }
channel.selected = false channel.selected = false
return channel return channel
@ -101,7 +101,7 @@ export default Vue.extend({
return 0 return 0
}).map((channel) => { }).map((channel) => {
if (this.backendPreference === 'invidious') { if (this.backendPreference === 'invidious') {
channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
} }
channel.selected = false channel.selected = false
return channel return channel

View File

@ -35,8 +35,8 @@ export default Vue.extend({
backendPreference: function () { backendPreference: function () {
return this.$store.getters.getBackendPreference return this.$store.getters.getBackendPreference
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
profileList: function () { profileList: function () {
return this.$store.getters.getProfileList return this.$store.getters.getProfileList
@ -73,7 +73,7 @@ export default Vue.extend({
return index === -1 return index === -1
}).map((channel) => { }).map((channel) => {
if (this.backendPreference === 'invidious') { if (this.backendPreference === 'invidious') {
channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
} }
channel.selected = false channel.selected = false
return channel return channel
@ -100,7 +100,7 @@ export default Vue.extend({
return index === -1 return index === -1
}).map((channel) => { }).map((channel) => {
if (this.backendPreference === 'invidious') { if (this.backendPreference === 'invidious') {
channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
} }
channel.selected = false channel.selected = false
return channel return channel

View File

@ -34,12 +34,12 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
invidiousURL() { invidiousURL() {
let videoUrl = `${this.invidiousInstance}/watch?v=${this.id}` let videoUrl = `${this.currentInvidiousInstance}/watch?v=${this.id}`
// `playlistId` can be undefined // `playlistId` can be undefined
if (this.playlistId && this.playlistId.length !== 0) { if (this.playlistId && this.playlistId.length !== 0) {
// `index` seems can be ignored // `index` seems can be ignored
@ -49,7 +49,7 @@ export default Vue.extend({
}, },
invidiousEmbedURL() { invidiousEmbedURL() {
return `${this.invidiousInstance}/embed/${this.id}` return `${this.currentInvidiousInstance}/embed/${this.id}`
}, },
youtubeURL() { youtubeURL() {

View File

@ -1,11 +1,11 @@
import Vue from 'vue' import Vue from 'vue'
import $ from 'jquery' import { mapActions, mapMutations } from 'vuex'
import { mapActions } from 'vuex'
import FtCard from '../ft-card/ft-card.vue' import FtCard from '../ft-card/ft-card.vue'
import FtSelect from '../ft-select/ft-select.vue' import FtSelect from '../ft-select/ft-select.vue'
import FtInput from '../ft-input/ft-input.vue' import FtInput from '../ft-input/ft-input.vue'
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import FtButton from '../ft-button/ft-button.vue'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
@ -16,11 +16,11 @@ export default Vue.extend({
'ft-select': FtSelect, 'ft-select': FtSelect,
'ft-input': FtInput, 'ft-input': FtInput,
'ft-toggle-switch': FtToggleSwitch, 'ft-toggle-switch': FtToggleSwitch,
'ft-flex-box': FtFlexBox 'ft-flex-box': FtFlexBox,
'ft-button': FtButton
}, },
data: function () { data: function () {
return { return {
showInvidiousInstances: false,
instanceNames: [], instanceNames: [],
instanceValues: [], instanceValues: [],
backendValues: [ backendValues: [
@ -62,8 +62,8 @@ export default Vue.extend({
return this.$store.getters.getUsingElectron return this.$store.getters.getUsingElectron
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
enableSearchSuggestions: function () { enableSearchSuggestions: function () {
return this.$store.getters.getEnableSearchSuggestions return this.$store.getters.getEnableSearchSuggestions
@ -101,6 +101,12 @@ export default Vue.extend({
regionValues: function () { regionValues: function () {
return this.$store.getters.getRegionValues return this.$store.getters.getRegionValues
}, },
invidiousInstancesList: function () {
return this.$store.getters.getInvidiousInstancesList
},
defaultInvidiousInstance: function () {
return this.$store.getters.getDefaultInvidiousInstance
},
localeOptions: function () { localeOptions: function () {
return ['system'].concat(Object.keys(this.$i18n.messages)) return ['system'].concat(Object.keys(this.$i18n.messages))
@ -147,44 +153,42 @@ export default Vue.extend({
} }
}, },
mounted: function () { mounted: function () {
const requestUrl = 'https://api.invidious.io/instances.json' this.setCurrentInvidiousInstanceBounce =
$.getJSON(requestUrl, (response) => { debounce(this.setCurrentInvidiousInstance, 500)
console.log(response)
const instances = response.filter((instance) => {
if (instance[0].includes('.onion') || instance[0].includes('.i2p') || instance[0].includes('yewtu.be')) {
return false
} else {
return true
}
})
this.instanceNames = instances.map((instance) => {
return instance[0]
})
this.instanceValues = instances.map((instance) => {
return instance[1].uri.replace(/\/$/, '')
})
this.showInvidiousInstances = true
}).fail((xhr, textStatus, error) => {
console.log(xhr)
console.log(textStatus)
console.log(requestUrl)
console.log(error)
})
this.updateInvidiousInstanceBounce = debounce(this.updateInvidiousInstance, 500)
}, },
beforeDestroy: function () { beforeDestroy: function () {
if (this.invidiousInstance === '') { if (this.currentInvidiousInstance === '') {
this.updateInvidiousInstance('https://invidious.snopyta.org') // FIXME: If we call an action from here, there's no guarantee it will finish
// before the component is destroyed, which could bring up some problems
// Since I can't see any way to await it (because lifecycle hooks must be
// synchronous), unfortunately, we have to copy/paste the logic
// from the `setRandomCurrentInvidiousInstance` action onto here
const instanceList = this.invidiousInstancesList
const randomIndex = Math.floor(Math.random() * instanceList.length)
this.setCurrentInvidiousInstance(instanceList[randomIndex])
} }
}, },
methods: { methods: {
handleInvidiousInstanceInput: function (input) { handleInvidiousInstanceInput: function (input) {
const invidiousInstance = input.replace(/\/$/, '') const instance = input.replace(/\/$/, '')
this.updateInvidiousInstanceBounce(invidiousInstance) this.setCurrentInvidiousInstanceBounce(instance)
},
handleSetDefaultInstanceClick: function () {
const instance = this.currentInvidiousInstance
this.updateDefaultInvidiousInstance(instance)
const message = this.$t('Default Invidious instance has been set to $')
this.showToast({
message: message.replace('$', instance)
})
},
handleClearDefaultInstanceClick: function () {
this.updateDefaultInvidiousInstance('')
this.showToast({
message: this.$t('Default Invidious instance has been cleared')
})
}, },
handlePreferredApiBackend: function (backend) { handlePreferredApiBackend: function (backend) {
@ -196,6 +200,10 @@ export default Vue.extend({
} }
}, },
...mapMutations([
'setCurrentInvidiousInstance'
]),
...mapActions([ ...mapActions([
'showToast', 'showToast',
'updateEnableSearchSuggestions', 'updateEnableSearchSuggestions',
@ -204,11 +212,11 @@ export default Vue.extend({
'updateCheckForBlogPosts', 'updateCheckForBlogPosts',
'updateBarColor', 'updateBarColor',
'updateBackendPreference', 'updateBackendPreference',
'updateDefaultInvidiousInstance',
'updateLandingPage', 'updateLandingPage',
'updateRegion', 'updateRegion',
'updateListType', 'updateListType',
'updateThumbnailPreference', 'updateThumbnailPreference',
'updateInvidiousInstance',
'updateForceLocalBackendForLegacy', 'updateForceLocalBackendForLegacy',
'updateCurrentLocale' 'updateCurrentLocale'
]) ])

View File

@ -88,21 +88,47 @@
</div> </div>
<ft-flex-box class="generalSettingsFlexBox"> <ft-flex-box class="generalSettingsFlexBox">
<ft-input <ft-input
:placeholder="$t('Settings.General Settings[\'Invidious Instance (Default is https://invidious.snopyta.org)\']')" :placeholder="$t('Settings.General Settings.Current Invidious Instance')"
:show-arrow="false" :show-arrow="false"
:show-label="true" :show-label="true"
:value="invidiousInstance" :value="currentInvidiousInstance"
:data-list="instanceValues" :data-list="invidiousInstancesList"
:tooltip="$t('Tooltips.General Settings.Invidious Instance')" :tooltip="$t('Tooltips.General Settings.Invidious Instance')"
@input="handleInvidiousInstanceInput" @input="handleInvidiousInstanceInput"
/> />
</ft-flex-box> </ft-flex-box>
<ft-flex-box> <ft-flex-box>
<a <div>
href="https://api.invidious.io" <a
> href="https://api.invidious.io"
{{ $t('Settings.General Settings.View all Invidious instance information') }} >
</a> {{ $t('Settings.General Settings.View all Invidious instance information') }}
</a>
</div>
</ft-flex-box>
<p
v-if="defaultInvidiousInstance !== ''"
class="center"
>
{{ $t('Settings.General Settings.The currently set default instance is $').replace('$', defaultInvidiousInstance) }}
</p>
<template v-else>
<p class="center">
{{ $t('Settings.General Settings.No default instance has been set') }}
</p>
<p class="center">
{{ $t('Settings.General Settings.Current instance will be randomized on startup') }}
</p>
</template>
<ft-flex-box>
<ft-button
:label="$t('Settings.General Settings.Set Current Instance as Default')"
@click="handleSetDefaultInstanceClick"
/>
<ft-button
:label="$t('Settings.General Settings.Clear Default Instance')"
@click="handleClearDefaultInstanceClick"
/>
</ft-flex-box> </ft-flex-box>
</ft-card> </ft-card>
</template> </template>

View File

@ -35,8 +35,8 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
listType: function () { listType: function () {
@ -95,7 +95,7 @@ export default Vue.extend({
methods: { methods: {
sharePlaylist: function (method) { sharePlaylist: function (method) {
const youtubeUrl = `https://youtube.com/playlist?list=${this.id}` const youtubeUrl = `https://youtube.com/playlist?list=${this.id}`
const invidiousUrl = `${this.invidiousInstance}/playlist?list=${this.id}` const invidiousUrl = `${this.currentInvidiousInstance}/playlist?list=${this.id}`
switch (method) { switch (method) {
case 'copyYoutube': case 'copyYoutube':

View File

@ -15,8 +15,8 @@ export default Vue.extend({
backendPreference: function () { backendPreference: function () {
return this.$store.getters.getBackendPreference return this.$store.getters.getBackendPreference
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
profileList: function () { profileList: function () {
return this.$store.getters.getProfileList return this.$store.getters.getProfileList
@ -38,7 +38,7 @@ export default Vue.extend({
return 0 return 0
}).map((channel) => { }).map((channel) => {
if (this.backendPreference === 'invidious') { if (this.backendPreference === 'invidious') {
channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) channel.thumbnail = channel.thumbnail.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
} }
return channel return channel

View File

@ -44,8 +44,8 @@ export default Vue.extend({
return this.$store.getters.getBarColor return this.$store.getters.getBarColor
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
backendFallback: function () { backendFallback: function () {

View File

@ -50,8 +50,8 @@ export default Vue.extend({
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
hideCommentLikes: function () { hideCommentLikes: function () {
return this.$store.getters.getHideCommentLikes return this.$store.getters.getHideCommentLikes
@ -276,7 +276,7 @@ export default Vue.extend({
const commentData = response.comments.map((comment) => { const commentData = response.comments.map((comment) => {
comment.showReplies = false comment.showReplies = false
comment.authorLink = comment.authorId comment.authorLink = comment.authorId
comment.authorThumb = comment.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) comment.authorThumb = comment.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
if (this.hideCommentLikes) { if (this.hideCommentLikes) {
comment.likes = null comment.likes = null
} else { } else {
@ -342,7 +342,7 @@ export default Vue.extend({
const commentData = response.comments.map((comment) => { const commentData = response.comments.map((comment) => {
comment.showReplies = false comment.showReplies = false
comment.authorLink = comment.authorId comment.authorLink = comment.authorId
comment.authorThumb = comment.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) comment.authorThumb = comment.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
if (this.hideCommentLikes) { if (this.hideCommentLikes) {
comment.likes = null comment.likes = null
} else { } else {

View File

@ -126,8 +126,8 @@ export default Vue.extend({
} }
}, },
computed: { computed: {
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
profileList: function () { profileList: function () {

View File

@ -24,6 +24,9 @@
@media only screen and (max-width: 680px) @media only screen and (max-width: 680px)
width: 90% width: 90%
.center
text-align: center
@media only screen and (max-width: 460px) @media only screen and (max-width: 460px)
.generalSettingsFlexBox, .playerSettingsFlexBox, .externalPlayerSettingsFlexBox .generalSettingsFlexBox, .playerSettingsFlexBox, .externalPlayerSettingsFlexBox
justify-content: flex-start justify-content: flex-start

View File

@ -1,19 +1,58 @@
import $ from 'jquery' import $ from 'jquery'
const state = { const state = {
currentInvidiousInstance: '',
invidiousInstancesList: null,
isGetChannelInfoRunning: false isGetChannelInfoRunning: false
} }
const getters = { const getters = {
getIsGetChannelInfoRunning (state) { getIsGetChannelInfoRunning(state) {
return state.isGetChannelInfoRunning return state.isGetChannelInfoRunning
},
getCurrentInvidiousInstance(state) {
return state.currentInvidiousInstance
},
getInvidiousInstancesList(state) {
return state.invidiousInstancesList
} }
} }
const actions = { const actions = {
invidiousAPICall ({ rootState }, payload) { async fetchInvidiousInstances({ commit }) {
const requestUrl = 'https://api.invidious.io/instances.json'
let response
try {
response = await $.getJSON(requestUrl)
} catch (err) {
console.log(err)
}
const instances = response.filter((instance) => {
if (instance[0].includes('.onion') || instance[0].includes('.i2p') || instance[0].includes('yewtu.be')) {
return false
} else {
return true
}
}).map((instance) => {
return instance[1].uri.replace(/\/$/, '')
})
commit('setInvidiousInstancesList', instances)
},
setRandomCurrentInvidiousInstance({ commit, state }) {
const instanceList = state.invidiousInstancesList
const randomIndex = Math.floor(Math.random() * instanceList.length)
commit('setCurrentInvidiousInstance', instanceList[randomIndex])
},
invidiousAPICall({ state }, payload) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const requestUrl = rootState.settings.invidiousInstance + '/api/v1/' + payload.resource + '/' + payload.id + '?' + $.param(payload.params) const requestUrl = state.currentInvidiousInstance + '/api/v1/' + payload.resource + '/' + payload.id + '?' + $.param(payload.params)
$.getJSON(requestUrl, (response) => { $.getJSON(requestUrl, (response) => {
resolve(response) resolve(response)
@ -27,7 +66,7 @@ const actions = {
}) })
}, },
invidiousGetChannelInfo ({ commit, dispatch }, channelId) { invidiousGetChannelInfo({ commit, dispatch }, channelId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
commit('toggleIsGetChannelInfoRunning') commit('toggleIsGetChannelInfoRunning')
@ -48,7 +87,7 @@ const actions = {
}) })
}, },
invidiousGetPlaylistInfo ({ commit, dispatch }, payload) { invidiousGetPlaylistInfo({ commit, dispatch }, payload) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
dispatch('invidiousAPICall', payload).then((response) => { dispatch('invidiousAPICall', payload).then((response) => {
resolve(response) resolve(response)
@ -61,7 +100,7 @@ const actions = {
}) })
}, },
invidiousGetVideoInformation ({ dispatch }, videoId) { invidiousGetVideoInformation({ dispatch }, videoId) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const payload = { const payload = {
resource: 'videos', resource: 'videos',
@ -81,8 +120,16 @@ const actions = {
} }
const mutations = { const mutations = {
toggleIsGetChannelInfoRunning (state) { toggleIsGetChannelInfoRunning(state) {
state.isGetChannelInfoRunning = !state.isGetChannelInfoRunning state.isGetChannelInfoRunning = !state.isGetChannelInfoRunning
},
setCurrentInvidiousInstance(state, value) {
state.currentInvidiousInstance = value
},
setInvidiousInstancesList(state, value) {
state.invidiousInstancesList = value
} }
} }

View File

@ -194,7 +194,6 @@ const state = {
hideVideoLikesAndDislikes: false, hideVideoLikesAndDislikes: false,
hideVideoViews: false, hideVideoViews: false,
hideWatchedSubs: false, hideWatchedSubs: false,
invidiousInstance: 'https://invidious.snopyta.org',
landingPage: 'subscriptions', landingPage: 'subscriptions',
listType: 'grid', listType: 'grid',
playNextVideo: false, playNextVideo: false,
@ -250,6 +249,15 @@ const stateWithSideEffects = {
} }
}, },
defaultInvidiousInstance: {
defaultValue: '',
sideEffectsHandler: ({ commit, getters }, value) => {
if (value !== '' && getters.getCurrentInvidiousInstance !== value) {
commit('setCurrentInvidiousInstance', value)
}
}
},
defaultVolume: { defaultVolume: {
defaultValue: 1, defaultValue: 1,
sideEffectsHandler: (_, value) => { sideEffectsHandler: (_, value) => {

View File

@ -73,8 +73,8 @@ export default Vue.extend({
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
sessionSearchHistory: function () { sessionSearchHistory: function () {
@ -350,17 +350,17 @@ export default Vue.extend({
} else { } else {
this.subCount = response.subCount this.subCount = response.subCount
} }
this.thumbnailUrl = response.authorThumbnails[3].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) this.thumbnailUrl = response.authorThumbnails[3].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
this.channelDescription = autolinker.link(response.description) this.channelDescription = autolinker.link(response.description)
this.relatedChannels = response.relatedChannels.map((channel) => { this.relatedChannels = response.relatedChannels.map((channel) => {
channel.authorThumbnails[channel.authorThumbnails.length - 1].url = channel.authorThumbnails[channel.authorThumbnails.length - 1].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) channel.authorThumbnails[channel.authorThumbnails.length - 1].url = channel.authorThumbnails[channel.authorThumbnails.length - 1].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
return channel return channel
}) })
this.latestVideos = response.latestVideos this.latestVideos = response.latestVideos
if (typeof (response.authorBanners) !== 'undefined') { if (typeof (response.authorBanners) !== 'undefined') {
this.bannerUrl = response.authorBanners[0].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) this.bannerUrl = response.authorBanners[0].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`)
} }
this.isLoading = false this.isLoading = false

View File

@ -34,8 +34,8 @@ export default Vue.extend({
backendFallback: function () { backendFallback: function () {
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
} }
}, },
watch: { watch: {
@ -131,7 +131,7 @@ export default Vue.extend({
viewCount: result.viewCount, viewCount: result.viewCount,
videoCount: result.videoCount, videoCount: result.videoCount,
channelName: result.author, channelName: result.author,
channelThumbnail: result.authorThumbnails[2].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`), channelThumbnail: result.authorThumbnails[2].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`),
channelId: result.authorId, channelId: result.authorId,
infoSource: 'invidious' infoSource: 'invidious'
} }

View File

@ -40,8 +40,8 @@ export default Vue.extend({
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
hideWatchedSubs: function () { hideWatchedSubs: function () {
@ -378,7 +378,7 @@ export default Vue.extend({
getChannelVideosInvidiousRSS: function (channel, failedAttempts = 0) { getChannelVideosInvidiousRSS: function (channel, failedAttempts = 0) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const parser = new Parser() const parser = new Parser()
const feedUrl = `${this.invidiousInstance}/feed/channel/${channel.id}` const feedUrl = `${this.currentInvidiousInstance}/feed/channel/${channel.id}`
parser.parseURL(feedUrl).then(async (feed) => { parser.parseURL(feedUrl).then(async (feed) => {
resolve(await Promise.all(feed.items.map((video) => { resolve(await Promise.all(feed.items.map((video) => {

View File

@ -31,8 +31,8 @@ export default Vue.extend({
backendFallback: function () { backendFallback: function () {
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
region: function () { region: function () {
return this.$store.getters.getRegion.toUpperCase() return this.$store.getters.getRegion.toUpperCase()

View File

@ -103,8 +103,8 @@ export default Vue.extend({
backendFallback: function () { backendFallback: function () {
return this.$store.getters.getBackendFallback return this.$store.getters.getBackendFallback
}, },
invidiousInstance: function () { currentInvidiousInstance: function () {
return this.$store.getters.getInvidiousInstance return this.$store.getters.getCurrentInvidiousInstance
}, },
proxyVideos: function () { proxyVideos: function () {
return this.$store.getters.getProxyVideos return this.$store.getters.getProxyVideos
@ -514,7 +514,7 @@ export default Vue.extend({
} }
this.dashSrc = this.createInvidiousDashManifest() this.dashSrc = this.createInvidiousDashManifest()
this.videoStoryboardSrc = `${this.invidiousInstance}/api/v1/storyboards/${this.videoId}?height=90` this.videoStoryboardSrc = `${this.currentInvidiousInstance}/api/v1/storyboards/${this.videoId}?height=90`
this.invidiousGetVideoInformation(this.videoId) this.invidiousGetVideoInformation(this.videoId)
.then(result => { .then(result => {
@ -540,14 +540,14 @@ export default Vue.extend({
} }
this.channelId = result.authorId this.channelId = result.authorId
this.channelName = result.author this.channelName = result.author
this.channelThumbnail = result.authorThumbnails[1] ? result.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.invidiousInstance}/ggpht/`) : '' this.channelThumbnail = result.authorThumbnails[1] ? result.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`) : ''
this.videoPublished = result.published * 1000 this.videoPublished = result.published * 1000
this.videoDescriptionHtml = result.descriptionHtml this.videoDescriptionHtml = result.descriptionHtml
this.recommendedVideos = result.recommendedVideos this.recommendedVideos = result.recommendedVideos
this.adaptiveFormats = result.adaptiveFormats this.adaptiveFormats = result.adaptiveFormats
this.isLive = result.liveNow this.isLive = result.liveNow
this.captionHybridList = result.captions.map(caption => { this.captionHybridList = result.captions.map(caption => {
caption.url = this.invidiousInstance + caption.url caption.url = this.currentInvidiousInstance + caption.url
caption.type = '' caption.type = ''
caption.dataSource = 'invidious' caption.dataSource = 'invidious'
return caption return caption
@ -555,13 +555,13 @@ export default Vue.extend({
switch (this.thumbnailPreference) { switch (this.thumbnailPreference) {
case 'start': case 'start':
this.thumbnail = `${this.invidiousInstance}/vi/${this.videoId}/maxres1.jpg` this.thumbnail = `${this.currentInvidiousInstance}/vi/${this.videoId}/maxres1.jpg`
break break
case 'middle': case 'middle':
this.thumbnail = `${this.invidiousInstance}/vi/${this.videoId}/maxres2.jpg` this.thumbnail = `${this.currentInvidiousInstance}/vi/${this.videoId}/maxres2.jpg`
break break
case 'end': case 'end':
this.thumbnail = `${this.invidiousInstance}/vi/${this.videoId}/maxres3.jpg` this.thumbnail = `${this.currentInvidiousInstance}/vi/${this.videoId}/maxres3.jpg`
break break
default: default:
this.thumbnail = result.videoThumbnails[0].url this.thumbnail = result.videoThumbnails[0].url
@ -1033,7 +1033,7 @@ export default Vue.extend({
}, },
createInvidiousDashManifest: function () { createInvidiousDashManifest: function () {
let url = `${this.invidiousInstance}/api/manifest/dash/id/${this.videoId}.mpd` let url = `${this.currentInvidiousInstance}/api/manifest/dash/id/${this.videoId}.mpd`
if (this.proxyVideos || !this.usingElectron) { if (this.proxyVideos || !this.usingElectron) {
url = url + '?local=true' url = url + '?local=true'

View File

@ -128,8 +128,13 @@ Settings:
Beginning: Beginning Beginning: Beginning
Middle: Middle Middle: Middle
End: End End: End
'Invidious Instance (Default is https://invidious.snopyta.org)': Invidious Instance Current Invidious Instance: Current Invidious Instance
(Default is https://invidious.snopyta.org) # $ is replaced with the default Invidious instance
The currently set default instance is $: The currently set default instance is $
No default instance has been set: No default instance has been set
Current instance will be randomized on startup: Current instance will be randomized on startup
Set Current Instance as Default: Set Current Instance as Default
Clear Default Instance: Clear Default Instance
View all Invidious instance information: View all Invidious instance information View all Invidious instance information: View all Invidious instance information
Region for Trending: Region for Trending Region for Trending: Region for Trending
#! List countries #! List countries
@ -647,6 +652,9 @@ Playing Next Video: Playing Next Video
Playing Previous Video: Playing Previous Video Playing Previous Video: Playing Previous Video
Playing Next Video Interval: Playing next video in no time. Click to cancel. | Playing next video in {nextVideoInterval} second. Click to cancel. | Playing next video in {nextVideoInterval} seconds. Click to cancel. Playing Next Video Interval: Playing next video in no time. Click to cancel. | Playing next video in {nextVideoInterval} second. Click to cancel. | Playing next video in {nextVideoInterval} seconds. Click to cancel.
Canceled next video autoplay: Canceled next video autoplay Canceled next video autoplay: Canceled next video autoplay
# $ is replaced with the default Invidious instance
Default Invidious instance has been set to $: Default Invidious instance has been set to $
Default Invidious instance has been cleared: Default Invidious instance has been cleared
'The playlist has ended. Enable loop to continue playing': 'The playlist has ended. Enable 'The playlist has ended. Enable loop to continue playing': 'The playlist has ended. Enable
loop to continue playing' loop to continue playing'