Add DeArrow support for ft-list-video titles (#3688)
* Add deArrow support for ft-list-video titles * Implement requested changes * Remove some code duplication Co-authored-by: PikachuEXE <pikachuexe@gmail.com> * fix lint issues + exception * Check for vote count when title is not locked Co-authored-by: Ajay Ramachandran <dev@ajay.app> * simplify title * Apply suggestions from code review Co-authored-by: PikachuEXE <pikachuexe@gmail.com> * remove unused clearDeArrowCache function * Fix deArrow title Co-authored-by: PikachuEXE <pikachuexe@gmail.com> * allow using DeArrow without enabling SB * add tooltip for DeArrow setting --------- Co-authored-by: PikachuEXE <pikachuexe@gmail.com> Co-authored-by: Ajay Ramachandran <dev@ajay.app>
This commit is contained in:
co-authored by
PikachuEXE
Ajay Ramachandran
parent
649525558b
commit
cf88bd77d8
@@ -10,6 +10,7 @@ import {
|
||||
toLocalePublicationString,
|
||||
toDistractionFreeTitle
|
||||
} from '../../helpers/utils'
|
||||
import { deArrowData } from '../../helpers/sponsorblock'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtListVideo',
|
||||
@@ -320,6 +321,14 @@ export default defineComponent({
|
||||
currentLocale: function () {
|
||||
return this.$i18n.locale.replace('_', '-')
|
||||
},
|
||||
|
||||
useDeArrowTitles: function () {
|
||||
return this.$store.getters.getUseDeArrowTitles
|
||||
},
|
||||
|
||||
deArrowCache: function () {
|
||||
return this.$store.getters.getDeArrowCache(this.id)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
historyIndex() {
|
||||
@@ -331,6 +340,25 @@ export default defineComponent({
|
||||
this.checkIfWatched()
|
||||
},
|
||||
methods: {
|
||||
getDeArrowDataEntry: async function() {
|
||||
// Read from local cache or remote
|
||||
// Write to cache if read from remote
|
||||
if (!this.useDeArrowTitles) { return null }
|
||||
|
||||
if (this.deArrowCache) { return this.deArrowCache }
|
||||
|
||||
const videoId = this.id
|
||||
const data = await deArrowData(this.id)
|
||||
const cacheData = { videoId, title: null }
|
||||
if (Array.isArray(data?.titles) && data.titles.length > 0 && (data.titles[0].locked || data.titles[0].votes > 0)) {
|
||||
cacheData.title = data.titles[0].title
|
||||
}
|
||||
|
||||
// Save data to cache whether data available or not to prevent duplicate requests
|
||||
this.$store.commit('addVideoToDeArrowCache', cacheData)
|
||||
return cacheData
|
||||
},
|
||||
|
||||
handleExternalPlayer: function () {
|
||||
this.$emit('pause-player')
|
||||
|
||||
@@ -401,9 +429,9 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
|
||||
parseVideoData: function () {
|
||||
parseVideoData: async function () {
|
||||
this.id = this.data.videoId
|
||||
this.title = this.data.title
|
||||
this.title = (await this.getDeArrowDataEntry())?.title ?? this.data.title
|
||||
// this.thumbnail = this.data.videoThumbnails[4].url
|
||||
|
||||
this.channelName = this.data.author ?? null
|
||||
|
||||
@@ -38,6 +38,10 @@ export default defineComponent({
|
||||
},
|
||||
sponsorBlockShowSkippedToast: function () {
|
||||
return this.$store.getters.getSponsorBlockShowSkippedToast
|
||||
},
|
||||
|
||||
useDeArrowTitles: function () {
|
||||
return this.$store.getters.getUseDeArrowTitles
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -45,6 +49,10 @@ export default defineComponent({
|
||||
this.updateUseSponsorBlock(value)
|
||||
},
|
||||
|
||||
handleUpdateUseDeArrowTitles: function (value) {
|
||||
this.updateUseDeArrowTitles(value)
|
||||
},
|
||||
|
||||
handleUpdateSponsorBlockUrl: function (value) {
|
||||
const sponsorBlockUrlWithoutTrailingSlash = value.replace(/\/$/, '')
|
||||
const sponsorBlockUrlWithoutApiSuffix = sponsorBlockUrlWithoutTrailingSlash.replace(/\/api$/, '')
|
||||
@@ -58,7 +66,8 @@ export default defineComponent({
|
||||
...mapActions([
|
||||
'updateUseSponsorBlock',
|
||||
'updateSponsorBlockUrl',
|
||||
'updateSponsorBlockShowSkippedToast'
|
||||
'updateSponsorBlockShowSkippedToast',
|
||||
'updateUseDeArrowTitles'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
@@ -8,11 +8,20 @@
|
||||
:default-value="useSponsorBlock"
|
||||
@change="handleUpdateSponsorBlock"
|
||||
/>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.SponsorBlock Settings.UseDeArrowTitles')"
|
||||
:default-value="useDeArrowTitles"
|
||||
:tooltip="$t('Tooltips.SponsorBlock Settings.UseDeArrowTitles')"
|
||||
@change="handleUpdateUseDeArrowTitles"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<div
|
||||
v-if="useSponsorBlock"
|
||||
v-if="useSponsorBlock || useDeArrowTitles"
|
||||
>
|
||||
<ft-flex-box class="settingsFlexStart500px">
|
||||
<ft-flex-box
|
||||
v-if="useSponsorBlock"
|
||||
class="settingsFlexStart500px"
|
||||
>
|
||||
<ft-toggle-switch
|
||||
:label="$t('Settings.SponsorBlock Settings.Notify when sponsor segment is skipped')"
|
||||
:default-value="sponsorBlockShowSkippedToast"
|
||||
@@ -28,7 +37,9 @@
|
||||
@input="handleUpdateSponsorBlockUrl"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<ft-flex-box>
|
||||
<ft-flex-box
|
||||
v-if="useSponsorBlock"
|
||||
>
|
||||
<ft-sponsor-block-category
|
||||
v-for="category in categories"
|
||||
:key="category"
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import store from '../store/index'
|
||||
|
||||
export async function sponsorBlockSkipSegments(videoId, categories) {
|
||||
async function getVideoHash(videoId) {
|
||||
const videoIdBuffer = new TextEncoder().encode(videoId)
|
||||
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', videoIdBuffer)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
|
||||
const videoIdHashPrefix = hashArray
|
||||
return hashArray
|
||||
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||
.slice(0, 4)
|
||||
.join('')
|
||||
|
||||
}
|
||||
export async function sponsorBlockSkipSegments(videoId, categories) {
|
||||
const videoIdHashPrefix = await getVideoHash(videoId)
|
||||
const requestUrl = `${store.getters.getSponsorBlockUrl}/api/skipSegments/${videoIdHashPrefix}?categories=${JSON.stringify(categories)}`
|
||||
|
||||
try {
|
||||
@@ -30,3 +31,23 @@ export async function sponsorBlockSkipSegments(videoId, categories) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function deArrowData(videoId) {
|
||||
const videoIdHashPrefix = (await getVideoHash(videoId)).substring(0, 4)
|
||||
const requestUrl = `${store.getters.getSponsorBlockUrl}/api/branding/${videoIdHashPrefix}`
|
||||
|
||||
try {
|
||||
const response = await fetch(requestUrl)
|
||||
|
||||
// 404 means that there are no segments registered for the video
|
||||
if (response.status === 404) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const json = await response.json()
|
||||
return json[videoId] ?? undefined
|
||||
} catch (error) {
|
||||
console.error('failed to fetch DeArrow data', requestUrl, error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,6 +286,7 @@ const state = {
|
||||
settingsPassword: '',
|
||||
allowDashAv1Formats: false,
|
||||
commentAutoLoadEnabled: false,
|
||||
useDeArrowTitles: false,
|
||||
}
|
||||
|
||||
const stateWithSideEffects = {
|
||||
|
||||
@@ -27,6 +27,7 @@ const state = {
|
||||
movies: null
|
||||
},
|
||||
cachedPlaylist: null,
|
||||
deArrowCache: {},
|
||||
showProgressBar: false,
|
||||
progressBarPercentage: 0,
|
||||
regionNames: [],
|
||||
@@ -57,6 +58,10 @@ const getters = {
|
||||
return state.sessionSearchHistory
|
||||
},
|
||||
|
||||
getDeArrowCache: (state) => (videoId) => {
|
||||
return state.deArrowCache[videoId]
|
||||
},
|
||||
|
||||
getPopularCache () {
|
||||
return state.popularCache
|
||||
},
|
||||
@@ -604,6 +609,18 @@ const mutations = {
|
||||
state.sessionSearchHistory = history
|
||||
},
|
||||
|
||||
setDeArrowCache (state, cache) {
|
||||
state.deArrowCache = cache
|
||||
},
|
||||
|
||||
addVideoToDeArrowCache (state, payload) {
|
||||
const sameVideo = state.deArrowCache[payload.videoId]
|
||||
|
||||
if (!sameVideo) {
|
||||
state.deArrowCache[payload.videoId] = payload
|
||||
}
|
||||
},
|
||||
|
||||
addToSessionSearchHistory (state, payload) {
|
||||
const sameSearch = state.sessionSearchHistory.findIndex((search) => {
|
||||
return search.query === payload.query && searchFiltersMatch(payload.searchSettings, search.searchSettings)
|
||||
|
||||
@@ -410,6 +410,7 @@ Settings:
|
||||
Enable SponsorBlock: Enable SponsorBlock
|
||||
'SponsorBlock API Url (Default is https://sponsor.ajay.app)': SponsorBlock API Url (Default is https://sponsor.ajay.app)
|
||||
Notify when sponsor segment is skipped: Notify when sponsor segment is skipped
|
||||
UseDeArrowTitles: Use DeArrow Video Titles
|
||||
Skip Options:
|
||||
Skip Option: Skip Option
|
||||
Auto Skip: Auto Skip
|
||||
@@ -841,6 +842,8 @@ Tooltips:
|
||||
when the watch page is closed.
|
||||
Experimental Settings:
|
||||
Replace HTTP Cache: Disables Electron's disk based HTTP cache and enables a custom in-memory image cache. Will lead to increased RAM usage.
|
||||
SponsorBlock Settings:
|
||||
UseDeArrowTitles: Replace video titles with user-submitted titles from DeArrow.
|
||||
|
||||
# Toast Messages
|
||||
Local API Error (Click to copy): Local API Error (Click to copy)
|
||||
|
||||
Reference in New Issue
Block a user