diff --git a/_scripts/getInstances.js b/_scripts/getInstances.js index d1b5bdb9ba961..a81d1b59e6240 100644 --- a/_scripts/getInstances.js +++ b/_scripts/getInstances.js @@ -14,3 +14,10 @@ fetch(invidiousApiUrl).then(e => e.json()).then(res => { }) fs.writeFile('././static/invidious-instances.json', JSON.stringify(data, null, 2)) }) + +const pipedApiUrl = 'https://piped-instances.kavin.rocks/' + +fetch(pipedApiUrl).then(e => e.json()).then(res => { + const data = res.map(e => e.api_url) + fs.writeFile('././static/piped-instances.json', JSON.stringify(data, null, 2)) +}) diff --git a/src/renderer/App.js b/src/renderer/App.js index 5846ca3a2184d..54f40b865d443 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -101,6 +101,9 @@ export default defineComponent({ defaultInvidiousInstance: function () { return this.$store.getters.getDefaultInvidiousInstance }, + defaultPipedInstance: function () { + return this.$store.getters.getDefaultPipedInstance + }, baseTheme: function () { return this.$store.getters.getBaseTheme @@ -175,8 +178,14 @@ export default defineComponent({ this.checkThemeSettings() await this.fetchInvidiousInstancesFromFile() + await this.fetchPipedInstancesFromFile() + if (this.defaultInvidiousInstance === '') { - await this.setRandomCurrentInvidiousInstance() + this.setRandomCurrentInvidiousInstance() + } + + if (this.defaultPipedInstance === '') { + this.setRandomCurrentPipedInstance() } this.fetchInvidiousInstances().then(e => { @@ -185,6 +194,12 @@ export default defineComponent({ } }) + this.fetchPipedInstances().then(e => { + if (this.defaultInvidiousInstance === '') { + this.setRandomCurrentInvidiousInstance() + } + }) + this.grabAllProfiles(this.$t('Profile.All Channels')).then(async () => { this.grabHistory() this.grabAllPlaylists() @@ -576,6 +591,9 @@ export default defineComponent({ 'fetchInvidiousInstancesFromFile', 'setAppTitle', 'setRandomCurrentInvidiousInstance', + 'fetchPipedInstances', + 'fetchPipedInstancesFromFile', + 'setRandomCurrentPipedInstance', 'setupListenersToSyncWindows', 'updateBaseTheme', 'updateMainColor', diff --git a/src/renderer/components/CommentSection/CommentSection.vue b/src/renderer/components/CommentSection/CommentSection.vue index 2810ae0993b55..47dd019776308 100644 --- a/src/renderer/components/CommentSection/CommentSection.vue +++ b/src/renderer/components/CommentSection/CommentSection.vue @@ -322,6 +322,8 @@ import { invidiousGetComments } from '../../helpers/api/invidious' +import { getPipedComments, getPipedCommentsMore } from '../../helpers/api/piped' + const { t } = useI18n() const props = defineProps({ @@ -363,7 +365,7 @@ const nextPageToken = shallowRef(null) // we need to react to new replies and showReplies being toggled const commentData = ref([]) -/** @type {import('vue').ComputedRef<'local' | 'invidious'>} */ +/** @type {import('vue').ComputedRef<'local' | 'invidious' | 'piped'>} */ const backendPreference = computed(() => { return store.getters.getBackendPreference }) @@ -373,6 +375,11 @@ const backendFallback = computed(() => { return store.getters.getBackendFallback }) +/** @type {import('vue').ComputedRef<'local' | 'invidious' | 'piped'>} */ +const fallbackPreference = computed(() => { + return store.getters.getFallbackPreference +}) + /** @type {import('vue').ComputedRef} */ const hideCommentLikes = computed(() => { return store.getters.getHideCommentLikes @@ -473,7 +480,9 @@ function isSubscribedToChannel(channelId) { function getCommentData() { isLoading.value = true - if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious' || props.isPostComments) { + if (backendPreference.value === 'piped' && !props.isPostComments) { + getCommentDataPiped() + } else if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious' || props.isPostComments) { if (!props.isPostComments) { getCommentDataInvidious() } else { @@ -488,7 +497,9 @@ function getMoreComments() { if (commentData.value.length === 0 || nextPageToken.value == null) { showToast(t('Comments.There are no more comments for this video')) } else { - if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious' || props.isPostComments) { + if (backendPreference.value === 'piped' && !props.isPostComments) { + getCommentDataPipedMore(nextPageToken.value) + } else if (!process.env.SUPPORTS_LOCAL_API || backendPreference.value === 'invidious' || props.isPostComments) { if (!props.isPostComments) { getCommentDataInvidious() } else { @@ -515,7 +526,9 @@ function toggleCommentReplies(index) { * @param {number} index */ function getCommentReplies(index) { - if (!process.env.SUPPORTS_LOCAL_API || commentData.value[index].dataType === 'invidious' || props.isPostComments) { + if (commentData.value[index].dataType === 'piped' && !props.isPostComments) { + getCommentDataPipedMore(replyTokens.get(commentData.value[index].id), index) + } else if (!process.env.SUPPORTS_LOCAL_API || commentData.value[index].dataType === 'invidious' || props.isPostComments) { if (!props.isPostComments) { getCommentRepliesInvidious(index) } else { @@ -585,8 +598,13 @@ async function getCommentDataLocal(more = false) { copyToClipboard(err) }) if (backendFallback.value && backendPreference.value === 'local') { - showToast(t('Falling back to Invidious API')) - getCommentDataInvidious() + if (fallbackPreference.value === 'invidious') { + showToast(t('Falling back to Invidious API')) + getCommentDataInvidious() + } else if (fallbackPreference.value === 'piped') { + showToast(t('Falling back to Piped API')) + getCommentDataPiped() + } } else { isLoading.value = false } @@ -634,8 +652,13 @@ async function getCommentRepliesLocal(index) { copyToClipboard(err) }) if (backendFallback.value && backendPreference.value === 'local') { - showToast(t('Falling back to Invidious API')) - getCommentDataInvidious() + if (fallbackPreference.value === 'invidious') { + showToast(t('Falling back to Invidious API')) + getCommentDataInvidious() + } else if (fallbackPreference.value === 'piped') { + showToast(t('Falling back to Piped API')) + getCommentDataPiped() + } } else { isLoading.value = false } @@ -684,9 +707,14 @@ async function getCommentDataInvidious() { copyToClipboard(err) }) - if (process.env.SUPPORTS_LOCAL_API && backendFallback.value && backendPreference.value === 'invidious') { - showToast(t('Falling back to Local API')) - getCommentDataLocal() + if (backendFallback.value && backendPreference.value === 'invidious') { + if (fallbackPreference.value === 'piped') { + showToast(t('Falling back to Piped API')) + getCommentDataPiped() + } else if (process.env.SUPPORTS_LOCAL_API && fallbackPreference.value === 'local') { + showToast(t('Falling back to Local API')) + getCommentDataLocal() + } } else { isLoading.value = false } @@ -790,6 +818,86 @@ async function getPostCommentRepliesInvidious(index) { isLoading.value = false } } + +async function getCommentDataPiped() { + try { + const { comments, continuation } = await getPipedComments(props.id) + comments.map((fullComment) => { + // Use destructuring to create a new object without the replyToken + const { replyToken, ...comment } = fullComment + if (comment.hasReplyToken) { + replyTokens.set(comment.id, replyToken) + } else { + replyTokens.delete(comment.id) + } + return comment + }) + + commentData.value = comments + nextPageToken.value = continuation + isLoading.value = false + showComments.value = true + } catch (err) { + console.error(err) + const errorMessage = t('Piped API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (backendFallback.value && backendPreference.value === 'piped') { + if (fallbackPreference.value === 'invidious') { + showToast(t('Falling back to Invidious API')) + getCommentDataInvidious() + } else if (process.env.SUPPORTS_LOCAL_API && fallbackPreference.value === 'local') { + showToast(t('Falling back to Local API')) + getCommentDataLocal() + } + } else { + isLoading.value = false + } + } +} + +async function getCommentDataPipedMore(token, index = null) { + try { + const { comments, continuation } = await getPipedCommentsMore({ + videoId: props.id, + continuation: token + }) + if (index !== null) { + const comment = commentData.value[index] + comment.replies = comment.replies.concat(comments) + comment.showReplies = true + if (comment.hasReplyToken) { + replyTokens.set(comment.id, continuation) + comment.hasReplyToken = true + } else { + replyTokens.delete(comment.id) + comment.hasReplyToken = false + } + } else { + commentData.value = commentData.value.concat(comments) + nextPageToken.value = continuation + } + isLoading.value = false + } catch (err) { + console.error(err) + const errorMessage = t('Piped API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (backendFallback.value && backendPreference.value === 'piped') { + if (fallbackPreference.value === 'invidious') { + showToast(t('Falling back to Invidious API')) + getCommentDataInvidious() + } else if (process.env.SUPPORTS_LOCAL_API && fallbackPreference.value === 'local') { + showToast(t('Falling back to Local API')) + getCommentDataLocal() + } + } else { + isLoading.value = false + } + } +}