Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 72 additions & 16 deletions src/renderer/components/FtListVideo/FtListVideo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,12 @@ const progressPercentage = computed(() => {
/** @type {import('vue').ComputedRef<any[]>} */
const hiddenChannels = computed(() => JSON.parse(store.getters.getChannelsHidden))

/** @type {import('vue').ComputedRef<boolean>} */
const useSponsorBlock = computed(() => store.getters.getUseSponsorBlock)

/** @type {import('vue').ComputedRef<any[]>} */
const sponsorBlockExcludedChannels = computed(() => JSON.parse(store.getters.getSponsorBlockExcludedChannels))

const playlistSharable = computed(() => {
// `playlistId` can be undefined
// User playlist ID should not be shared
Expand Down Expand Up @@ -583,24 +589,46 @@ const dropdownOptions = computed(() => {
}
}

if (channelId.value !== null && !inSubscriptions.value) {
const channelShouldBeHidden = hiddenChannels.value.some(c => c.name === channelId.value)
if (channelId.value !== null) {
if (!inSubscriptions.value) {
const channelShouldBeHidden = hiddenChannels.value.some(c => c.name === channelId.value)

options.push(
{
type: 'divider'
},
options.push(
{
type: 'divider'
},

channelShouldBeHidden
? {
label: t('Video.Unhide Channel'),
value: 'unhideChannel'
}
: {
label: t('Video.Hide Channel'),
value: 'hideChannel'
}
)
channelShouldBeHidden
? {
label: t('Video.Unhide Channel'),
value: 'unhideChannel'
}
: {
label: t('Video.Hide Channel'),
value: 'hideChannel'
}
)
}

if (useSponsorBlock.value) {
const isSponsorBlockChannelExcluded = sponsorBlockExcludedChannels.value.some(c => c.name === channelId.value)

options.push(
{
type: 'divider'
},

isSponsorBlockChannelExcluded
? {
label: t('Video.Enable SponsorBlock on Channel'),
value: 'enableSponsorBlockOnChannel'
}
: {
label: t('Video.Disable SponsorBlock on Channel'),
value: 'disableSponsorBlockOnChannel'
}
)
}
}

return options
Expand Down Expand Up @@ -698,6 +726,12 @@ function handleOptionsClick(option) {
case 'unhideChannel':
unhideChannel(channelName.value, channelId.value)
break
case 'disableSponsorBlockOnChannel':
disableSponsorBlockOnChannel(channelName.value, channelId.value)
break
case 'enableSponsorBlockOnChannel':
enableSponsorBlockOnChannel(channelName.value, channelId.value)
break
}
}

Expand Down Expand Up @@ -1133,6 +1167,28 @@ function unhideChannel(channelName, channelId) {
showToast(t('Channel Unhidden', { channel: channelName }))
}

/**
* @param {string} channelName
* @param {string} channelId
*/
function disableSponsorBlockOnChannel(channelName, channelId) {
const newExcludedChannels = [...sponsorBlockExcludedChannels.value, { name: channelId, preferredName: channelName }]

store.dispatch('updateSponsorBlockExcludedChannels', JSON.stringify(newExcludedChannels))

showToast(t('SponsorBlock Disabled on Channel', { channel: channelName }))
}

/**
* @param {string} channelName
* @param {string} channelId
*/
function enableSponsorBlockOnChannel(channelName, channelId) {
store.dispatch('updateSponsorBlockExcludedChannels', JSON.stringify(sponsorBlockExcludedChannels.value.filter(c => c.name !== channelId)))

showToast(t('SponsorBlock Enabled on Channel', { channel: channelName }))
}

function toggleQuickBookmarked() {
if (!isQuickBookmarkEnabled.value) {
// This should be prevented by UI
Expand Down
110 changes: 109 additions & 1 deletion src/renderer/components/SponsorBlockSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@
@blur="handleUpdateSponsorBlockUrl"
/>
</FtFlexBox>
<FtFlexBox>
<FtInputTags
:disabled="sponsorBlockExcludedChannelsDisabled"
:disabled-msg="t('Settings.SponsorBlock Settings.Excluded Channels.Disabled Message')"
:label="t('Settings.SponsorBlock Settings.Excluded Channels.Excluded Channels')"
:tag-name-placeholder="t('Settings.Distraction Free Settings.Hide Channels Placeholder')"
:tag-list="sponsorBlockExcludedChannels"
:tooltip="t('Settings.SponsorBlock Settings.Excluded Channels.Tooltip')"
:validate-tag-name="checkYoutubeChannelId"
:find-tag-info="findChannelTagInfoWrapper"
:are-channel-tags="true"
:show-tags="sponsorBlockShowExcludedChannels"
@invalid-name="handleInvalidChannel"
@error-find-tag-info="handleChannelAPIError"
@change="handleSponsorBlockExcludedChannels"
@already-exists="handleChannelsExists"
@toggle-show-tags="handleSponsorBlockShowExcludedChannels"
/>
</FtFlexBox>
<FtFlexBox
v-if="useDeArrowThumbnails"
>
Expand Down Expand Up @@ -72,16 +91,23 @@
</template>

<script setup>
import { computed, useTemplateRef } from 'vue'
import { computed, ref, useTemplateRef, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'

import FtSettingsSection from './FtSettingsSection/FtSettingsSection.vue'
import FtToggleSwitch from './FtToggleSwitch/FtToggleSwitch.vue'
import FtInput from './FtInput/FtInput.vue'
import FtFlexBox from './ft-flex-box/ft-flex-box.vue'
import FtSponsorBlockCategory from './FtSponsorBlockCategory/FtSponsorBlockCategory.vue'
import FtInputTags from './FtInputTags/FtInputTags.vue'

import store from '../store/index'

import { showToast } from '../helpers/utils'
import { checkYoutubeChannelId, findChannelTagInfo } from '../helpers/channels.js'

const { t } = useI18n()

const CATEGORIES = [
'sponsor',
'self-promotion',
Expand All @@ -93,6 +119,8 @@ const CATEGORIES = [
'filler'
]

const sponsorBlockExcludedChannelsDisabled = ref(false)

/** @type {import('vue').ComputedRef<boolean>} */
const useSponsorBlock = computed(() => store.getters.getUseSponsorBlock)

Expand All @@ -114,6 +142,38 @@ const deArrowThumbnailGeneratorUrl = computed(() => store.getters.getDeArrowThum
const sponsorBlockUrlInputRef = useTemplateRef('sponsorBlockUrlInput')
const deArrowThumbnailGeneratorUrlRef = useTemplateRef('deArrowThumbnailGeneratorUrl')

/** @type {import('vue').ComputedRef<any[]>} */
const sponsorBlockExcludedChannels = computed(() => JSON.parse(store.getters.getSponsorBlockExcludedChannels))

/** @type {import('vue').ComputedRef<boolean>} */
const sponsorBlockShowExcludedChannels = computed(() => store.getters.getSponsorBlockShowExcludedChannels)

/** @type {import('vue').ComputedRef<'local' | 'invidious'>} */
const backendPreference = computed(() => store.getters.getBackendPreference)

/** @type {import('vue').ComputedRef<boolean>} */
const backendFallback = computed(() => store.getters.getBackendFallback)

const backendOptions = computed(() => ({
preference: backendPreference.value,
fallback: backendFallback.value
}))

onMounted(() => {
verifySponsorBlockExcludedChannels()
})

/**
* @param {any[]} value
*/
function handleSponsorBlockExcludedChannels(value) {
store.dispatch('updateSponsorBlockExcludedChannels', JSON.stringify(value))
}

function handleSponsorBlockShowExcludedChannels() {
store.dispatch('updateSponsorBlockShowExcludedChannels', !sponsorBlockShowExcludedChannels.value)
}

/**
* @param {boolean} value
*/
Expand Down Expand Up @@ -166,6 +226,18 @@ function handleUpdateDeArrowThumbnailGeneratorUrl(value) {
}
}

function handleInvalidChannel() {
showToast(t('Settings.Distraction Free Settings.Hide Channels Invalid'))
}

function handleChannelAPIError() {
showToast(t('Settings.Distraction Free Settings.Hide Channels API Error'))
}

function handleChannelsExists() {
showToast(t('Settings.Distraction Free Settings.Hide Channels Already Exists'))
}

/**
* @param {string} url
*/
Expand All @@ -174,4 +246,40 @@ function cleanupUrl(url) {
.replace(/\/+$/, '')
.replace(/\/api$/, '')
}

/**
* @param {string} text
*/
async function findChannelTagInfoWrapper(text) {
return await findChannelTagInfo(text, backendOptions.value)
}

async function verifySponsorBlockExcludedChannels() {
const excludedChannelsCpy = [...sponsorBlockExcludedChannels.value]

for (let i = 0; i < excludedChannelsCpy.length; i++) {
const tag = excludedChannelsCpy[i]

// if channel has been processed and confirmed as non existent, skip
if (tag.invalid) continue

// process if no preferred name and is possibly a YouTube ID
if ((tag.preferredName === '' || !tag.icon) && checkYoutubeChannelId(tag.name)) {
sponsorBlockExcludedChannelsDisabled.value = true

const { preferredName, icon, iconHref, invalidId } = await findChannelTagInfoWrapper(tag.name)
if (invalidId) {
excludedChannelsCpy[i] = { name: tag.name, invalid: invalidId }
} else {
excludedChannelsCpy[i] = { name: tag.name, preferredName, icon, iconHref }
}

// update on every tag in case it closes
handleSponsorBlockExcludedChannels(excludedChannelsCpy)
}
}

sponsorBlockExcludedChannelsDisabled.value = false
}

</script>
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export default defineComponent({
type: String,
default: ''
},
channelId: {
type: String,
default: ''
},
title: {
type: String,
default: ''
Expand Down Expand Up @@ -375,11 +379,21 @@ export default defineComponent({
return store.getters.getVideoSkipMouseScroll
})

/** @type {import('vue').ComputedRef<any[]>} */
const sponsorBlockExcludedChannels = computed(() => {
return JSON.parse(store.getters.getSponsorBlockExcludedChannels)
})

/** @type {import('vue').ComputedRef<boolean>} */
const useSponsorBlock = computed(() => {
return store.getters.getUseSponsorBlock
})

/** @type {import('vue').ComputedRef<boolean>} */
const shouldSkipSponsorBlockSegmentsOnChannel = computed(() => {
return useSponsorBlock.value && !sponsorBlockExcludedChannels.value.some(c => c.name === props.channelId)
})

/** @type {import('vue').ComputedRef<boolean>} */
const sponsorBlockShowSkippedToast = computed(() => {
return store.getters.getSponsorBlockShowSkippedToast
Expand Down Expand Up @@ -516,6 +530,10 @@ export default defineComponent({
* @param {number} currentTime
*/
function skipSponsorBlockSegments(currentTime) {
if (!shouldSkipSponsorBlockSegmentsOnChannel.value) {
return
}

const { autoSkip } = sponsorSkips.value

if (autoSkip.size === 0) {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/store/modules/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ const state = {
useDeArrowTitles: false,
useDeArrowThumbnails: false,
deArrowThumbnailGeneratorUrl: 'https://dearrow-thumb.ajay.app',
sponsorBlockExcludedChannels: '[]',
sponsorBlockShowExcludedChannels: true,
// This makes the `favorites` playlist uses as quick bookmark target
// If the playlist is removed quick bookmark is disabled
quickBookmarkTargetPlaylistId: 'favorites',
Expand Down
1 change: 1 addition & 0 deletions src/renderer/views/Watch/Watch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
:format="activeFormat"
:thumbnail="thumbnail"
:video-id="videoId"
:channel-id="channelId"
:chapters="videoChapters"
:current-chapter-index="videoCurrentChapterIndex"
:chapters-src="chaptersSrc"
Expand Down
9 changes: 9 additions & 0 deletions static/locales/en-US.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,11 @@ Settings:
Prompt To Skip: Prompt To Skip
Do Nothing: Do Nothing
Category Color: Category Color
Excluded Channels:
Disabled Message: Some channels were excluded using ID and weren't processed. Feature is blocked while those IDs are updating
Excluded Channels: Excluded Channels
Tooltip: Enter a channel ID to have SponsorBlock disabled. Sponsor segments in videos from this channel won't be skipped.
The channel ID entered must be a complete match and is case sensitive.
Parental Control Settings:
Parental Control Settings: Parental Control
Hide Unsubscribe Button: Hide Unsubscribe Button
Expand Down Expand Up @@ -870,6 +875,8 @@ Video:
Copy Invidious Channel Link: Copy Invidious Channel Link
Hide Channel: Hide Channel
Unhide Channel: Show Channel
Enable SponsorBlock on Channel: Enable SponsorBlock on Channel
Disable SponsorBlock on Channel: Disable SponsorBlock on Channel
Views: Views
Loop Playlist: Loop Playlist
Shuffle Playlist: Shuffle Playlist
Expand Down Expand Up @@ -1144,6 +1151,8 @@ Screenshot Error: Screenshot failed. {error}
Screenshot Clipboard Error: Screenshot copy to clipboard failed
Channel Hidden: '{channel} added to channel filter'
Channel Unhidden: '{channel} removed from channel filter'
SponsorBlock Disabled on Channel: 'SponsorBlock disabled on {channel}'
SponsorBlock Enabled on Channel: 'SponsorBlock enabled on {channel}'
Trimmed input must be at least N characters long: Trimmed input must be at least 1 character long | Trimmed input must be at least {length} characters long
Tag already exists: '"{tagName}" tag already exists'

Expand Down
Loading