import Vue from 'vue' import { mapActions } from 'vuex' import FtCard from '../ft-card/ft-card.vue' import FtLoader from '../../components/ft-loader/ft-loader.vue' import FtSelect from '../../components/ft-select/ft-select.vue' import FtTimestampCatcher from '../../components/ft-timestamp-catcher/ft-timestamp-catcher.vue' import autolinker from 'autolinker' import ytcm from '@freetube/yt-comment-scraper' export default Vue.extend({ name: 'WatchVideoComments', components: { 'ft-card': FtCard, 'ft-loader': FtLoader, 'ft-select': FtSelect, 'ft-timestamp-catcher': FtTimestampCatcher }, props: { id: { type: String, required: true }, channelName: { type: String, required: true }, channelThumbnail: { type: String, required: true } }, data: function () { return { isLoading: false, showComments: false, commentScraper: null, nextPageToken: null, commentData: [], sortNewest: false, commentProcess: null, sortingChanged: false } }, computed: { isDev: function () { return process.env.NODE_ENV === 'development' }, backendPreference: function () { return this.$store.getters.getBackendPreference }, backendFallback: function () { return this.$store.getters.getBackendFallback }, currentInvidiousInstance: function () { return this.$store.getters.getCurrentInvidiousInstance }, hideCommentLikes: function () { return this.$store.getters.getHideCommentLikes }, sortNames: function () { return [ this.$t('Comments.Top comments'), this.$t('Comments.Newest first') ] }, sortValues: function () { return [ 'top', 'newest' ] }, currentSortValue: function () { return (this.sortNewest) ? 'newest' : 'top' } }, beforeDestroy: function () { if (this.commentProcess !== null) { this.commentProcess.send('end') } }, methods: { onTimestamp: function (timestamp) { this.$emit('timestamp-event', timestamp) }, handleSortChange: function (sortType) { this.sortNewest = !this.sortNewest switch (this.backendPreference) { case 'local': this.isLoading = true this.commentData = [] this.nextPageToken = undefined this.getCommentDataLocal({ videoId: this.id, setCookie: false, sortByNewest: this.sortNewest, continuation: this.nextPageToken ? this.nextPageToken : undefined }) break case 'invidious': this.isLoading = true this.commentData = [] this.getCommentDataInvidious({ resource: 'comments', id: this.id, params: { continuation: this.nextPageToken, sort_by: this.sortNewest ? 'new' : 'top' } }) break } }, getCommentData: function () { this.isLoading = true switch (this.backendPreference) { case 'local': this.getCommentDataLocal({ videoId: this.id, setCookie: false, sortByNewest: this.sortNewest, continuation: this.nextPageToken ? this.nextPageToken : undefined }) break case 'invidious': this.getCommentDataInvidious({ resource: 'comments', id: this.id, params: { continuation: this.nextPageToken, sort_by: this.sortNewest ? 'new' : 'top' } }) break } }, getMoreComments: function () { if (this.commentData.length === 0 || this.nextPageToken === null || typeof this.nextPageToken === 'undefined') { this.showToast({ message: this.$t('Comments.There are no more comments for this video') }) } else { this.getCommentData() } }, toggleCommentReplies: function (index) { if (this.commentData[index].showReplies || this.commentData[index].replies.length > 0) { this.commentData[index].showReplies = !this.commentData[index].showReplies } else { this.getCommentReplies(index) } }, getCommentReplies: function (index) { switch (this.commentData[index].dataType) { case 'local': this.getCommentRepliesLocal({ videoId: this.id, setCookie: false, sortByNewest: this.sortNewest, replyToken: this.commentData[index].replyToken, index: index }) break case 'invidious': this.getCommentRepliesInvidious(index) break } }, getCommentDataLocal: function (payload) { ytcm.getComments(payload).then((response) => { this.parseLocalCommentData(response, null) }).catch((err) => { console.log(err) const errorMessage = this.$t('Local API Error (Click to copy)') this.showToast({ message: `${errorMessage}: ${err}`, time: 10000, action: () => { navigator.clipboard.writeText(err) } }) if (this.backendFallback && this.backendPreference === 'local') { this.showToast({ message: this.$t('Falling back to Invidious API') }) this.getCommentDataInvidious({ resource: 'comments', id: this.id, params: { continuation: this.nextPageToken, sort_by: this.sortNewest ? 'new' : 'top' } }) } else { this.isLoading = false } }) }, getCommentRepliesLocal: function (payload) { this.showToast({ message: this.$t('Comments.Getting comment replies, please wait') }) ytcm.getCommentReplies(payload).then((response) => { this.parseLocalCommentData(response, payload.index) }).catch((err) => { console.log(err) const errorMessage = this.$t('Local API Error (Click to copy)') this.showToast({ message: `${errorMessage}: ${err}`, time: 10000, action: () => { navigator.clipboard.writeText(err) } }) if (this.backendFallback && this.backendPreference === 'local') { this.showToast({ message: this.$t('Falling back to Invidious API') }) this.getCommentDataInvidious({ resource: 'comments', id: this.id, params: { continuation: this.nextPageToken, sort_by: this.sortNewest ? 'new' : 'top' } }) } else { this.isLoading = false } }) }, parseLocalCommentData: function (response, index = null) { const commentData = response.comments.map((comment) => { comment.authorLink = comment.authorId comment.showReplies = false comment.authorThumb = comment.authorThumb[0].url comment.replies = [] comment.dataType = 'local' this.toLocalePublicationString({ publishText: (comment.time + ' ago'), templateString: this.$t('Video.Publicationtemplate'), timeStrings: this.$t('Video.Published'), liveStreamString: this.$t('Video.Watching'), upcomingString: this.$t('Video.Published.Upcoming'), isLive: false, isUpcoming: false, isRSS: false }).then((data) => { comment.time = data }).catch((error) => { console.error(error) }) if (this.hideCommentLikes) { comment.likes = null } comment.text = autolinker.link(comment.text.replace(/(<(?!br>)([^>]+)>)/ig, '')) return comment }) if (index !== null) { this.commentData[index].replies = this.commentData[index].replies.concat(commentData) this.commentData[index].replyToken = response.continuation this.commentData[index].showReplies = true } else { if (this.sortingChanged) { this.commentData = [] this.sortingChanged = false } this.commentData = this.commentData.concat(commentData) this.isLoading = false this.showComments = true this.nextPageToken = response.continuation } }, getCommentDataInvidious: function (payload) { this.invidiousAPICall(payload).then((response) => { const commentData = response.comments.map((comment) => { comment.showReplies = false comment.authorLink = comment.authorId comment.authorThumb = comment.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`) if (this.hideCommentLikes) { comment.likes = null } else { comment.likes = comment.likeCount } comment.text = autolinker.link(comment.content.replace(/(<(?!br>)([^>]+)>)/ig, '')) comment.dataType = 'invidious' comment.isOwner = comment.authorIsChannelOwner if (typeof (comment.replies) !== 'undefined' && typeof (comment.replies.replyCount) !== 'undefined') { comment.numReplies = comment.replies.replyCount comment.replyContinuation = comment.replies.continuation } else { comment.numReplies = 0 comment.replyContinuation = '' } comment.replies = [] comment.time = comment.publishedText return comment }) this.commentData = this.commentData.concat(commentData) this.nextPageToken = response.continuation this.isLoading = false this.showComments = true }).catch((xhr) => { console.log('found an error') console.log(xhr) const errorMessage = this.$t('Invidious API Error (Click to copy)') this.showToast({ message: `${errorMessage}: ${xhr.responseText}`, time: 10000, action: () => { navigator.clipboard.writeText(xhr.responseText) } }) if (this.backendFallback && this.backendPreference === 'invidious') { this.showToast({ message: this.$t('Falling back to local API') }) this.getCommentDataLocal() } else { this.isLoading = false } }) }, getCommentRepliesInvidious: function (index) { this.showToast({ message: this.$t('Comments.Getting comment replies, please wait') }) const payload = { resource: 'comments', id: this.id, params: { continuation: this.commentData[index].replyContinuation } } this.invidiousAPICall(payload).then((response) => { const commentData = response.comments.map((comment) => { comment.showReplies = false comment.authorLink = comment.authorId comment.authorThumb = comment.authorThumbnails[1].url.replace('https://yt3.ggpht.com', `${this.currentInvidiousInstance}/ggpht/`) if (this.hideCommentLikes) { comment.likes = null } else { comment.likes = comment.likeCount } comment.text = autolinker.link(comment.content.replace(/(<(?!br>)([^>]+)>)/ig, '')) comment.time = comment.publishedText comment.dataType = 'invidious' comment.numReplies = 0 comment.replyContinuation = '' comment.replies = [] return comment }) this.commentData[index].replies = commentData this.commentData[index].showReplies = true this.isLoading = false }).catch((xhr) => { console.log('found an error') console.log(xhr) const errorMessage = this.$t('Invidious API Error (Click to copy)') this.showToast({ message: `${errorMessage}: ${xhr.responseText}`, time: 10000, action: () => { navigator.clipboard.writeText(xhr.responseText) } }) this.isLoading = false }) }, goToChannel: function (channelId) { this.$router.push({ path: `/channel/${channelId}` }) }, ...mapActions([ 'showToast', 'toLocalePublicationString', 'invidiousAPICall' ]) } })