diff --git a/package.json b/package.json index f90edb335..d69ed3d0c 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "@fortawesome/free-brands-svg-icons": "^6.2.1", "@fortawesome/free-solid-svg-icons": "^6.2.1", "@fortawesome/vue-fontawesome": "^2.0.9", - "@freetube/yt-comment-scraper": "^6.2.0", "@silvermine/videojs-quality-selector": "^1.2.5", "autolinker": "^4.0.0", "browserify": "^17.0.0", diff --git a/src/renderer/components/watch-video-comments/watch-video-comments.js b/src/renderer/components/watch-video-comments/watch-video-comments.js index bad2f65b5..9ea5a3e00 100644 --- a/src/renderer/components/watch-video-comments/watch-video-comments.js +++ b/src/renderer/components/watch-video-comments/watch-video-comments.js @@ -3,15 +3,9 @@ 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' -import { - copyToClipboard, - showToast, - stripHTML, - toLocalePublicationString -} from '../../helpers/utils' +import { copyToClipboard, showToast } from '../../helpers/utils' import { invidiousGetCommentReplies, invidiousGetComments } from '../../helpers/api/invidious' +import { getLocalComments, parseLocalComment } from '../../helpers/api/local' export default defineComponent({ name: 'WatchVideoComments', @@ -39,12 +33,9 @@ export default defineComponent({ return { isLoading: false, showComments: false, - commentScraper: null, nextPageToken: null, commentData: [], - sortNewest: false, - commentProcess: null, - sortingChanged: false + sortNewest: false } }, computed: { @@ -82,52 +73,23 @@ export default defineComponent({ } }, - beforeDestroy: function () { - if (this.commentProcess !== null) { - this.commentProcess.send('end') - } - }, methods: { onTimestamp: function (timestamp) { this.$emit('timestamp-event', timestamp) }, - handleSortChange: function (sortType) { + handleSortChange: function () { 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() - break - } + this.commentData = [] + this.getCommentData() }, 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() - break + if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') { + this.getCommentDataInvidious() + } else { + this.getCommentDataLocal() } }, @@ -135,7 +97,11 @@ export default defineComponent({ if (this.commentData.length === 0 || this.nextPageToken === null || typeof this.nextPageToken === 'undefined') { showToast(this.$t('Comments.There are no more comments for this video')) } else { - this.getCommentData() + if (!process.env.IS_ELECTRON || this.backendPreference === 'invidious') { + this.getCommentDataInvidious() + } else { + this.getCommentDataLocal(true) + } } }, @@ -148,100 +114,87 @@ export default defineComponent({ }, 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 + if (process.env.IS_ELECTRON) { + switch (this.commentData[index].dataType) { + case 'local': + this.getCommentRepliesLocal(index) + break + case 'invidious': + this.getCommentRepliesInvidious(index) + break + } + } else { + this.getCommentRepliesInvidious(index) } }, - getCommentDataLocal: function (payload) { - ytcm.getComments(payload).then((response) => { - this.parseLocalCommentData(response, null) - }).catch((err) => { - console.error(err) - const errorMessage = this.$t('Local API Error (Click to copy)') - showToast(`${errorMessage}: ${err}`, 10000, () => { - copyToClipboard(err) - }) - if (this.backendFallback && this.backendPreference === 'local') { - showToast(this.$t('Falling back to Invidious API')) - this.getCommentDataInvidious() + getCommentDataLocal: async function (more) { + try { + /** @type {import('youtubei.js/dist/src/parser/youtube/Comments').default} */ + let comments + if (more) { + comments = await this.nextPageToken.getContinuation() } else { - this.isLoading = false + comments = await getLocalComments(this.id, this.sortNewest) } - }) - }, - getCommentRepliesLocal: function (payload) { - showToast(this.$t('Comments.Getting comment replies, please wait')) + const parsedComments = comments.contents + .map(commentThread => parseLocalComment(commentThread.comment, commentThread)) - ytcm.getCommentReplies(payload).then((response) => { - this.parseLocalCommentData(response, payload.index) - }).catch((err) => { - console.error(err) - const errorMessage = this.$t('Local API Error (Click to copy)') - showToast(`${errorMessage}: ${err}`, 10000, () => { - copyToClipboard(err) - }) - if (this.backendFallback && this.backendPreference === 'local') { - showToast(this.$t('Falling back to Invidious API')) - this.getCommentDataInvidious() + if (more) { + this.commentData = this.commentData.concat(parsedComments) } 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' - comment.time = toLocalePublicationString({ - publishText: (comment.time + ' ago') - }) - - if (this.hideCommentLikes) { - comment.likes = null + this.commentData = parsedComments } - comment.text = autolinker.link(stripHTML(comment.text)) - if (comment.customEmojis.length > 0) { - comment.customEmojis.forEach(emoji => { - comment.text = comment.text.replace(emoji.text, ``) - }) - } - - return comment - }) - - if (index !== null) { - if (this.commentData[index].replies.length === 0 || this.commentData[index].replies[this.commentData[index].replies.length - 1].commentId !== commentData[commentData.length - 1].commentId) { - 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.nextPageToken = comments.has_continuation ? comments : null this.isLoading = false this.showComments = true - this.nextPageToken = response.continuation + } catch (err) { + console.error(err) + const errorMessage = this.$t('Local API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (this.backendFallback && this.backendPreference === 'local') { + showToast(this.$t('Falling back to Invidious API')) + this.getCommentDataInvidious() + } else { + this.isLoading = false + } + } + }, + + getCommentRepliesLocal: async function (index) { + showToast(this.$t('Comments.Getting comment replies, please wait')) + + try { + const comment = this.commentData[index] + /** @type {import('youtubei.js/dist/src/parser/classes/comments/CommentThread').default} */ + const commentThread = comment.replyToken + + if (comment.replies.length > 0) { + await commentThread.getContinuation() + comment.replies = comment.replies.concat(commentThread.replies.map(reply => parseLocalComment(reply))) + } else { + await commentThread.getReplies() + comment.replies = commentThread.replies.map(reply => parseLocalComment(reply)) + } + + comment.replyToken = commentThread.has_continuation ? commentThread : null + comment.showReplies = true + } catch (err) { + console.error(err) + const errorMessage = this.$t('Local API Error (Click to copy)') + showToast(`${errorMessage}: ${err}`, 10000, () => { + copyToClipboard(err) + }) + if (this.backendFallback && this.backendPreference === 'local') { + showToast(this.$t('Falling back to Invidious API')) + this.getCommentDataInvidious() + } else { + this.isLoading = false + } } }, @@ -263,12 +216,7 @@ export default defineComponent({ }) if (process.env.IS_ELECTRON && this.backendFallback && this.backendPreference === 'invidious') { showToast(this.$t('Falling back to local API')) - this.getCommentDataLocal({ - videoId: this.id, - setCookie: false, - sortByNewest: this.sortNewest, - continuation: this.nextPageToken ? this.nextPageToken : undefined - }) + this.getCommentDataLocal() } else { this.isLoading = false } diff --git a/src/renderer/components/watch-video-comments/watch-video-comments.vue b/src/renderer/components/watch-video-comments/watch-video-comments.vue index 3e46ec668..65962828e 100644 --- a/src/renderer/components/watch-video-comments/watch-video-comments.vue +++ b/src/renderer/components/watch-video-comments/watch-video-comments.vue @@ -104,11 +104,14 @@ @timestamp-event="onTimestamp" />
-
-
-
- `)
} else {
- const { text, endpoint } = run
+ const { text, bold, italics, strikethrough, endpoint } = run
if (endpoint && !text.startsWith('#')) {
switch (endpoint.metadata.page_type) {
@@ -379,13 +389,15 @@ export function parseLocalTextRuns(runs, emojiSize = 16) {
parsedRuns.push(`https://www.youtube.com${endpoint.metadata.url}`)
}
break
- case 'WEB_PAGE_TYPE_CHANNEL':
- if (text.startsWith('@')) {
- parsedRuns.push(`${text}`)
+ case 'WEB_PAGE_TYPE_CHANNEL': {
+ const trimmedText = text.trim()
+ if (trimmedText.startsWith('@')) {
+ parsedRuns.push(`${trimmedText}`)
} else {
parsedRuns.push(`https://www.youtube.com${endpoint.metadata.url}`)
}
break
+ }
case 'WEB_PAGE_TYPE_PLAYLIST':
parsedRuns.push(`https://www.youtube.com${endpoint.metadata.url}`)
break
@@ -411,7 +423,20 @@ export function parseLocalTextRuns(runs, emojiSize = 16) {
}
}
} else {
- parsedRuns.push(text)
+ let formattedText = text
+ if (bold) {
+ formattedText = `${formattedText}`
+ }
+
+ if (italics) {
+ formattedText = `${formattedText}`
+ }
+
+ if (strikethrough) {
+ formattedText = `${formattedText}`
+ }
+
+ parsedRuns.push(formattedText)
}
}
}
@@ -437,3 +462,36 @@ export function mapLocalFormat(format) {
url: format.url
}
}
+
+/**
+ * @param {import('youtubei.js/dist/src/parser/classes/comments/Comment').default} comment
+ * @param {import('youtubei.js/dist/src/parser/classes/comments/CommentThread').default} commentThread
+ */
+export function parseLocalComment(comment, commentThread = undefined) {
+ let hasOwnerReplied = false
+ let replyToken = null
+
+ if (commentThread?.has_replies) {
+ hasOwnerReplied = commentThread.comment_replies_data.has_channel_owner_replied
+ replyToken = commentThread
+ }
+
+ return {
+ dataType: 'local',
+ authorLink: comment.author.id,
+ author: comment.author.name,
+ authorThumb: comment.author.best_thumbnail.url,
+ isPinned: comment.is_pinned,
+ isOwner: comment.author_is_channel_owner,
+ isMember: comment.is_member,
+ text: Autolinker.link(parseLocalTextRuns(comment.content.runs, 16)),
+ time: toLocalePublicationString({ publishText: comment.published.text.replace('(edited)', '').trim() }),
+ likes: comment.vote_count,
+ isHearted: comment.is_hearted,
+ numReplies: comment.reply_count,
+ hasOwnerReplied,
+ replyToken,
+ showReplies: false,
+ replies: []
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 7e35637d0..ca0c578f2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1058,13 +1058,6 @@
resolved "https://registry.yarnpkg.com/@fortawesome/vue-fontawesome/-/vue-fontawesome-2.0.9.tgz#7df35818135be54fd87568f16ec7b998a3d0cce8"
integrity sha512-tUmO92PFHbLOplitjHNBVGMJm6S57vp16tBXJVPKSI/6CfjrgLycqKxEpC6f7qsOqUdoXs5nIv4HLUfrOMHzuw==
-"@freetube/yt-comment-scraper@^6.2.0":
- version "6.2.0"
- resolved "https://registry.yarnpkg.com/@freetube/yt-comment-scraper/-/yt-comment-scraper-6.2.0.tgz#ed11d65111d03076ff842eb9c3eb25413f8632ab"
- integrity sha512-69mBsvQ50rUBTUDfR6s1OaiH2sEmmx9T0uV2Qpp0ckMhL0sxxMZtxujraHV2FHDtOm5jG7uygi3Gseb54C9DGw==
- dependencies:
- axios "^0.27.2"
-
"@humanwhocodes/config-array@^0.11.8":
version "0.11.8"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.8.tgz#03595ac2075a4dc0f191cc2131de14fbd7d410b9"
@@ -2080,14 +2073,6 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
-axios@^0.27.2:
- version "0.27.2"
- resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
- integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
- dependencies:
- follow-redirects "^1.14.9"
- form-data "^4.0.0"
-
axios@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35"
@@ -4395,11 +4380,6 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4"
integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ==
-follow-redirects@^1.14.9:
- version "1.15.1"
- resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
- integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
-
follow-redirects@^1.15.0:
version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"