From eb6d5a42838b3e9859e69a533cab9ca0fe54e98a Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 27 Aug 2022 16:32:09 +0200 Subject: [PATCH 1/4] add option to block channels --- src/components/PreferencesPage.vue | 15 +++++++++++++++ src/components/SearchResults.vue | 13 ++++++++++++- src/composables/useChannels.js | 5 +++++ src/locales/en.json | 3 ++- 4 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 src/composables/useChannels.js diff --git a/src/components/PreferencesPage.vue b/src/components/PreferencesPage.vue index 6aad95173c..f5cdfd14fb 100644 --- a/src/components/PreferencesPage.vue +++ b/src/components/PreferencesPage.vue @@ -389,6 +389,20 @@
+

+

@@ -597,6 +611,7 @@ const codecOptions = [ ]; const disableLBRY = usePreferenceBoolean("disableLBRY", false); const proxyLBRY = usePreferenceBoolean("proxyLBRY", false); +const blockedChannels = usePreferenceString("blockedChannels", ""); const prefetchLimit = usePreferenceNumber("prefetchLimit", 2); const password = ref(null); const showConfirmResetPrefsDialog = ref(false); diff --git a/src/components/SearchResults.vue b/src/components/SearchResults.vue index f2e58cde16..e66987a6c9 100644 --- a/src/components/SearchResults.vue +++ b/src/components/SearchResults.vue @@ -29,7 +29,9 @@ class="mx-2 grid grid-cols-1 gap-y-5 max-md:gap-x-3 sm:mx-0 sm:grid-cols-2 md:grid-cols-3 md:gap-x-6 lg:grid-cols-4 xl:grid-cols-5" > @@ -40,6 +42,7 @@ import { useRoute, useRouter } from "vue-router"; import ContentItem from "./ContentItem.vue"; import LoadingIndicatorPage from "./LoadingIndicatorPage.vue"; import { fetchJson, apiUrl } from "@/composables/useApi.js"; +import { isChannelBlocked } from "@/composables/useChannels.js"; import { getPreferenceBoolean } from "@/composables/usePreferences.js"; import { updateWatched } from "@/composables/useMisc.js"; @@ -129,6 +132,14 @@ function saveQueryToHistory() { localStorage.setItem("search_history", JSON.stringify(searchHistory)); } +function isBlocked(result) { + if (!result.uploaderUrl) { + // item is a channel + return isChannelBlocked(result.url); + } + return isChannelBlocked(result.uploaderUrl); +} + onMounted(() => { if (handleRedirect()) return; updateResults(); diff --git a/src/composables/useChannels.js b/src/composables/useChannels.js new file mode 100644 index 0000000000..3b5862a5cc --- /dev/null +++ b/src/composables/useChannels.js @@ -0,0 +1,5 @@ +export function isChannelBlocked(channelId) { + const blockedChannels = localStorage.getItem("blockedChannels"); + if (!blockedChannels) return false; + return blockedChannels.split(",").includes(channelId.replace("/channel/", "")); +} diff --git a/src/locales/en.json b/src/locales/en.json index 4db4740fd2..69252e5bbf 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -177,7 +177,8 @@ "show_password": "Show password", "hide_password": "Hide password", "rss_feed": "RSS feed", - "playlist_rss_feed": "Playlist RSS feed" + "playlist_rss_feed": "Playlist RSS feed", + "blocked_channels": "Blocked channels" }, "comment": { "pinned_by": "Pinned by {author}", From 8bc06b4920bfc6441d40d23d9eaa8f89a520131f Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 27 Aug 2022 16:44:01 +0200 Subject: [PATCH 2/4] block channels in trending too --- src/components/PreferencesPage.vue | 4 ++-- src/components/VideoItem.vue | 6 ++++++ src/locales/en.json | 5 +++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/PreferencesPage.vue b/src/components/PreferencesPage.vue index f5cdfd14fb..1713bf5213 100644 --- a/src/components/PreferencesPage.vue +++ b/src/components/PreferencesPage.vue @@ -389,7 +389,7 @@
-

+

diff --git a/src/components/VideoItem.vue b/src/components/VideoItem.vue index 8c2c619184..0ab1036818 100644 --- a/src/components/VideoItem.vue +++ b/src/components/VideoItem.vue @@ -134,6 +134,7 @@ import PlaylistAddModal from "./PlaylistAddModal.vue"; import ShareModal from "./ShareModal.vue"; import ConfirmModal from "./ConfirmModal.vue"; import VideoThumbnail from "./VideoThumbnail.vue"; +import { isChannelBlocked } from "@/composables/useChannels.js"; import { numberFormat, timeAgo } from "@/composables/useFormatting.js"; import { getPreferenceBoolean } from "@/composables/usePreferences.js"; import { removeVideoFromPlaylist } from "@/composables/usePlaylists.js"; @@ -180,6 +181,11 @@ function removeVideo() { } function shouldShowVideo() { + if (isChannelBlocked(props.item.uploaderUrl)) { + showVideo.value = false; + return; + } + if (!props.isFeed || !getPreferenceBoolean("hideWatched", false)) return; const objectStore = window.db.transaction("watch_history", "readonly").objectStore("watch_history"); diff --git a/src/locales/en.json b/src/locales/en.json index 69252e5bbf..402b9da7a1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -17,7 +17,8 @@ "bookmarks": "Bookmarks", "channel_groups": "Channel groups", "dearrow": "DeArrow", - "custom_instances": "Custom instances" + "custom_instances": "Custom instances", + "misc": "Miscellaneous" }, "player": { "watch_on": "View on {0}", @@ -178,7 +179,7 @@ "hide_password": "Hide password", "rss_feed": "RSS feed", "playlist_rss_feed": "Playlist RSS feed", - "blocked_channels": "Blocked channels" + "blocked_channels": "Blocked channels (IDs, seperated by ',')" }, "comment": { "pinned_by": "Pinned by {author}", From 943a7272a3ba4f3fdba318d41cd5865cac531b12 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 2 May 2026 22:01:59 +0200 Subject: [PATCH 3/4] use IndexedDb for blocks, block ui --- src/App.vue | 6 ++- src/components/ChannelPage.vue | 18 +++++++++ src/components/PreferencesPage.vue | 51 +++++++++++++++++------- src/components/SearchResults.vue | 13 +----- src/components/VideoItem.vue | 9 +++-- src/composables/useChannels.js | 64 ++++++++++++++++++++++++++++-- src/locales/en.json | 9 +++-- 7 files changed, 131 insertions(+), 39 deletions(-) diff --git a/src/App.vue b/src/App.vue index 10c3e02b7b..4b30c10c57 100644 --- a/src/App.vue +++ b/src/App.vue @@ -90,7 +90,7 @@ onMounted(() => { darkModePreference.addEventListener("change", handlePreferredColorSchemeChange); if ("indexedDB" in window) { - const request = indexedDB.open("piped-db", 6); + const request = indexedDB.open("piped-db", 7); request.onupgradeneeded = ev => { const db = request.result; console.log("Upgrading object store."); @@ -118,6 +118,10 @@ onMounted(() => { const playlistVideosStore = db.createObjectStore("playlist_videos", { keyPath: "videoId" }); playlistVideosStore.createIndex("videoId", "videoId", { unique: true }); } + if (!db.objectStoreNames.contains("blocked_channels")) { + const store = db.createObjectStore("blocked_channels", { keyPath: "channelId" }); + store.createIndex("channelId", "channelId", { unique: true }); + } // migration to fix an invalid previous length of channel ids: 11 -> 24 (async () => { if (ev.oldVersion < 6) { diff --git a/src/components/ChannelPage.vue b/src/components/ChannelPage.vue index 646a2deedf..8dd6dbf96e 100644 --- a/src/components/ChannelPage.vue +++ b/src/components/ChannelPage.vue @@ -18,6 +18,12 @@
+ +
-

- +

+
+ + + + + + + + + + +
+ +
+ + + +

@@ -486,6 +496,7 @@ import { usePreferenceString, } from "@/composables/usePreferences"; import { fetchJson, apiUrl, authApiUrl, getAuthToken, hashCode, isAuthenticated } from "@/composables/useApi"; +import { getBlockedChannels, removeBlockedChannel } from "@/composables/useChannels.js"; import { getCustomInstances } from "@/composables/useCustomInstances"; import { download } from "@/composables/useMisc"; import { getDefaultLanguage } from "@/composables/useFormatting"; @@ -516,6 +527,7 @@ const authInstance = usePreferenceBoolean("authInstance", false); const selectedAuthInstance = usePreferenceString("auth_instance_url", selectedInstance.value); const customInstances = ref([]); const publicInstances = ref([]); +const blockedChannels = ref([]); const sponsorBlock = usePreferenceBoolean("sponsorblock", true); const skipOptionsStorage = usePreferenceJSON("skipOptions", null); const skipOptions = ref(createDefaultSkipOptions()); @@ -611,7 +623,6 @@ const codecOptions = [ ]; const disableLBRY = usePreferenceBoolean("disableLBRY", false); const proxyLBRY = usePreferenceBoolean("proxyLBRY", false); -const blockedChannels = usePreferenceString("blockedChannels", ""); const prefetchLimit = usePreferenceNumber("prefetchLimit", 2); const password = ref(null); const showConfirmResetPrefsDialog = ref(false); @@ -682,6 +693,7 @@ onMounted(async () => { if (Object.keys(route.query).length > 0) router.replace({ query: {} }); fetchInstances(); + loadBlockedChannels(); if (testLocalStorage()) { skipOptions.value = normalizeSkipOptions(skipOptionsStorage.value); @@ -730,6 +742,15 @@ async function fetchInstances() { }); } +async function loadBlockedChannels() { + blockedChannels.value = await getBlockedChannels(); +} + +async function unblockChannel(channelId) { + removeBlockedChannel(channelId); + blockedChannels.value = blockedChannels.value.filter(channel => channel.channelId !== channelId); +} + function sslScore(url) { return "https://www.ssllabs.com/ssltest/analyze.html?d=" + new URL(url).host + "&latest"; } diff --git a/src/components/SearchResults.vue b/src/components/SearchResults.vue index e66987a6c9..f2e58cde16 100644 --- a/src/components/SearchResults.vue +++ b/src/components/SearchResults.vue @@ -29,9 +29,7 @@ class="mx-2 grid grid-cols-1 gap-y-5 max-md:gap-x-3 sm:mx-0 sm:grid-cols-2 md:grid-cols-3 md:gap-x-6 lg:grid-cols-4 xl:grid-cols-5" > @@ -42,7 +40,6 @@ import { useRoute, useRouter } from "vue-router"; import ContentItem from "./ContentItem.vue"; import LoadingIndicatorPage from "./LoadingIndicatorPage.vue"; import { fetchJson, apiUrl } from "@/composables/useApi.js"; -import { isChannelBlocked } from "@/composables/useChannels.js"; import { getPreferenceBoolean } from "@/composables/usePreferences.js"; import { updateWatched } from "@/composables/useMisc.js"; @@ -132,14 +129,6 @@ function saveQueryToHistory() { localStorage.setItem("search_history", JSON.stringify(searchHistory)); } -function isBlocked(result) { - if (!result.uploaderUrl) { - // item is a channel - return isChannelBlocked(result.url); - } - return isChannelBlocked(result.uploaderUrl); -} - onMounted(() => { if (handleRedirect()) return; updateResults(); diff --git a/src/components/VideoItem.vue b/src/components/VideoItem.vue index 0ab1036818..ff7fd2ca98 100644 --- a/src/components/VideoItem.vue +++ b/src/components/VideoItem.vue @@ -181,10 +181,11 @@ function removeVideo() { } function shouldShowVideo() { - if (isChannelBlocked(props.item.uploaderUrl)) { - showVideo.value = false; - return; - } + isChannelBlocked(props.item.uploaderUrl).then(blocked => { + if (blocked) { + showVideo.value = false; + } + }); if (!props.isFeed || !getPreferenceBoolean("hideWatched", false)) return; diff --git a/src/composables/useChannels.js b/src/composables/useChannels.js index 3b5862a5cc..790afa81bd 100644 --- a/src/composables/useChannels.js +++ b/src/composables/useChannels.js @@ -1,5 +1,61 @@ -export function isChannelBlocked(channelId) { - const blockedChannels = localStorage.getItem("blockedChannels"); - if (!blockedChannels) return false; - return blockedChannels.split(",").includes(channelId.replace("/channel/", "")); +function normalizeId(channelId) { + return channelId.replace("/channel/", ""); +} + +export function isChannelBlocked(channelUrl) { + return new Promise(resolve => { + var tx = window.db.transaction("blocked_channels", "readonly"); + var store = tx.objectStore("blocked_channels"); + store.count(normalizeId(channelUrl)).onsuccess = e => { + const result = e.target.result; + resolve(result > 0); + }; + }); +} + +export function addBlockedChannel(channelId, name) { + var tx = window.db.transaction("blocked_channels", "readwrite"); + var store = tx.objectStore("blocked_channels"); + store.add({ channelId, name }); +} + +export function removeBlockedChannel(channelId) { + var tx = window.db.transaction("blocked_channels", "readwrite"); + var store = tx.objectStore("blocked_channels"); + store.delete(channelId); +} + +export function getBlockedChannels() { + return new Promise(resolve => { + let blockedChannels = []; + var tx = window.db.transaction("blocked_channels", "readonly"); + var store = tx.objectStore("blocked_channels"); + const cursor = store.index("channelId").openCursor(); + cursor.onsuccess = e => { + const cursor = e.target.result; + if (cursor) { + blockedChannels.push(cursor.value); + cursor.continue(); + } else { + resolve(blockedChannels); + } + }; + }); +} + +export async function toggleChannelBlock(channelUrl, name) { + const channelId = normalizeId(channelUrl); + return new Promise(resolve => { + var tx = window.db.transaction("blocked_channels", "readwrite"); + var store = tx.objectStore("blocked_channels"); + store.count(channelId).onsuccess = e => { + const result = e.target.result; + if (result > 0) { + store.delete(channelId); + } else { + store.add({ channelId, name }); + } + resolve(result === 0); + }; + }); } diff --git a/src/locales/en.json b/src/locales/en.json index 402b9da7a1..10e01d5430 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -18,7 +18,7 @@ "channel_groups": "Channel groups", "dearrow": "DeArrow", "custom_instances": "Custom instances", - "misc": "Miscellaneous" + "blocked_channels": "Blocked Channels" }, "player": { "watch_on": "View on {0}", @@ -179,7 +179,9 @@ "hide_password": "Hide password", "rss_feed": "RSS feed", "playlist_rss_feed": "Playlist RSS feed", - "blocked_channels": "Blocked channels (IDs, seperated by ',')" + "blocked_channels": "Blocked channels", + "block": "Block", + "unblock": "Unblock" }, "comment": { "pinned_by": "Pinned by {author}", @@ -196,7 +198,8 @@ "up_to_date": "Up to date?", "ssl_score": "SSL Score", "uptime_30d": "Uptime (30d)", - "api_url": "API URL" + "api_url": "API URL", + "channel": "Channel" }, "login": { "username": "Username", From ff8912bfb5c7d7725f8eceee836f3fa75689bd65 Mon Sep 17 00:00:00 2001 From: Robin Appelman Date: Sat, 2 May 2026 22:06:08 +0200 Subject: [PATCH 4/4] still show videos from blocked channel on channel page --- src/components/VideoItem.vue | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/VideoItem.vue b/src/components/VideoItem.vue index ff7fd2ca98..01b9210130 100644 --- a/src/components/VideoItem.vue +++ b/src/components/VideoItem.vue @@ -181,11 +181,13 @@ function removeVideo() { } function shouldShowVideo() { - isChannelBlocked(props.item.uploaderUrl).then(blocked => { - if (blocked) { - showVideo.value = false; - } - }); + if (window.location.pathname !== props.item.uploaderUrl) { + isChannelBlocked(props.item.uploaderUrl).then(blocked => { + if (blocked) { + showVideo.value = false; + } + }); + } if (!props.isFeed || !getPreferenceBoolean("hideWatched", false)) return;