feat(parser): Support CommentView nodes (#614)

This commit is contained in:
absidue
2024-03-25 11:20:29 +01:00
committed by GitHub
parent 7ca2a0c3e4
commit 900f557202
5 changed files with 147 additions and 10 deletions

View File

@@ -2,6 +2,7 @@ import { Parser } from '../../index.js';
import Button from '../Button.js';
import ContinuationItem from '../ContinuationItem.js';
import Comment from './Comment.js';
import CommentView from './CommentView.js';
import CommentReplies from './CommentReplies.js';
import { InnertubeError } from '../../../utils/Utils.js';
@@ -17,15 +18,20 @@ export default class CommentThread extends YTNode {
#actions?: Actions;
#continuation?: ContinuationItem;
comment: Comment | null;
replies?: ObservedArray<Comment>;
comment: Comment | CommentView | null;
replies?: ObservedArray<Comment | CommentView>;
comment_replies_data: CommentReplies | null;
is_moderated_elq_comment: boolean;
has_replies: boolean;
constructor(data: RawNode) {
super();
this.comment = Parser.parseItem(data.comment, Comment);
if (Reflect.has(data, 'commentViewModel')) {
this.comment = Parser.parseItem(data.commentViewModel, CommentView);
} else {
this.comment = Parser.parseItem(data.comment, Comment);
}
this.comment_replies_data = Parser.parseItem(data.replies, CommentReplies);
this.is_moderated_elq_comment = data.isModeratedElqComment;
this.has_replies = !!this.comment_replies_data;
@@ -51,7 +57,7 @@ export default class CommentThread extends YTNode {
if (!response.on_response_received_endpoints_memo)
throw new InnertubeError('Unexpected response.', response);
this.replies = observe(response.on_response_received_endpoints_memo.getType(Comment).map((comment) => {
this.replies = observe(response.on_response_received_endpoints_memo.getType(Comment, CommentView).map((comment) => {
comment.setActions(this.#actions);
return comment;
}));

View File

@@ -0,0 +1,88 @@
import { YTNode } from '../../helpers.js';
import type { RawNode } from '../../index.js';
import type Actions from '../../../core/Actions.js';
import Author from '../misc/Author.js';
import Text from '../misc/Text.js';
export default class CommentView extends YTNode {
static type = 'CommentView';
#actions?: Actions;
comment_id: string;
is_pinned: boolean;
keys: {
comment: string;
comment_surface: string;
toolbar_state: string;
toolbar_surface: string;
shared: string;
};
content?: Text;
published_time?: string;
author_is_channel_owner?: boolean;
like_count?: string;
reply_count?: string;
is_member?: boolean;
member_badge?: {
url: string,
a11y: string;
};
author?: Author;
is_liked?: boolean;
is_disliked?: boolean;
is_hearted?: boolean;
constructor(data: RawNode) {
super();
this.comment_id = data.commentId;
this.is_pinned = !!data.pinnedText;
this.keys = {
comment: data.commentKey,
comment_surface: data.commentSurfaceKey,
toolbar_state: data.toolbarStateKey,
toolbar_surface: data.toolbarSurfaceKey,
shared: data.sharedKey
};
}
applyMutations(comment?: RawNode, toolbar_state?: RawNode) {
if (comment) {
this.content = Text.fromAttributed(comment.properties.content);
this.published_time = comment.properties.publishedTime;
this.author_is_channel_owner = !!comment.author.isCreator;
this.like_count = comment.toolbar.likeCountNotliked ? comment.toolbar.likeCountNotliked : '0';
this.reply_count = comment.toolbar.replyCount ? comment.toolbar.replyCount : '0';
this.is_member = !!comment.author.sponsorBadgeUrl;
if (Reflect.has(comment.author, 'sponsorBadgeUrl')) {
this.member_badge = {
url: comment.author.sponsorBadgeUrl,
a11y: comment.author.A11y
};
}
this.author = new Author({
simpleText: comment.author.displayName,
navigationEndpoint: comment.avatar.endpoint
}, comment.author, comment.avatar.image, comment.author.channelId);
}
if (toolbar_state) {
this.is_hearted = toolbar_state.heartState === 'TOOLBAR_HEART_STATE_HEARTED';
this.is_liked = toolbar_state.likeState === 'TOOLBAR_LIKE_STATE_LIKED';
this.is_disliked = toolbar_state.likeState === 'TOOLBAR_HEART_STATE_DISLIKED';
}
}
setActions(actions: Actions | undefined) {
this.#actions = actions;
}
}

View File

@@ -25,10 +25,21 @@ export default class Author {
this.name = nav_text?.text || 'N/A';
this.thumbnails = thumbs ? Thumbnail.fromResponse(thumbs) : [];
this.endpoint = ((nav_text?.runs?.[0] as TextRun) as TextRun)?.endpoint || nav_text?.endpoint;
this.badges = Array.isArray(badges) ? Parser.parseArray(badges) : observe([] as YTNode[]);
this.is_moderator = this.badges?.some((badge: any) => badge.icon_type == 'MODERATOR');
this.is_verified = this.badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED');
this.is_verified_artist = this.badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST');
if (badges) {
if (Array.isArray(badges)) {
this.badges = Parser.parseArray(badges);
this.is_moderator = this.badges?.some((badge: any) => badge.icon_type == 'MODERATOR');
this.is_verified = this.badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED');
this.is_verified_artist = this.badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST');
} else {
this.badges = observe([] as YTNode[]);
this.is_verified = !!badges.isVerified;
this.is_verified_artist = !!badges.isArtist;
}
} else {
this.badges = observe([] as YTNode[]);
}
// @TODO: Refactor this mess.
this.url =

View File

@@ -78,6 +78,7 @@ export { default as CommentsHeader } from './classes/comments/CommentsHeader.js'
export { default as CommentSimplebox } from './classes/comments/CommentSimplebox.js';
export { default as CommentsSimplebox } from './classes/comments/CommentsSimplebox.js';
export { default as CommentThread } from './classes/comments/CommentThread.js';
export { default as CommentView } from './classes/comments/CommentView.js';
export { default as CreatorHeart } from './classes/comments/CreatorHeart.js';
export { default as EmojiPicker } from './classes/comments/EmojiPicker.js';
export { default as PdgCommentChip } from './classes/comments/PdgCommentChip.js';

View File

@@ -25,6 +25,7 @@ import MusicMultiSelectMenuItem from './classes/menus/MusicMultiSelectMenuItem.j
import Format from './classes/misc/Format.js';
import VideoDetails from './classes/misc/VideoDetails.js';
import NavigationEndpoint from './classes/NavigationEndpoint.js';
import CommentView from './classes/comments/CommentView.js';
import type { KeyInfo } from './generator.js';
import type { ObservedArray, YTNodeConstructor, YTNode } from './helpers.js';
@@ -43,7 +44,8 @@ export type ParserError = {
classdata: RawNode,
error: unknown
} | {
error_type: 'mutation_data_missing'
error_type: 'mutation_data_missing',
classname: string
} | {
error_type: 'mutation_data_invalid',
total: number,
@@ -108,7 +110,7 @@ let ERROR_HANDLER: ParserErrorHandler = ({ classname, ...context }: ParserError)
case 'mutation_data_missing':
Log.warn(TAG,
new InnertubeError(
'Mutation data required for processing MusicMultiSelectMenuItems, but none found.\n' +
`Mutation data required for processing ${classname}, but none found.\n` +
`This is a bug, please report it at ${Platform.shim.info.bugs_url}`
)
);
@@ -316,6 +318,10 @@ export function parseResponse<T extends IParsedResponse = IParsedResponse>(data:
applyMutations(contents_memo, data.frameworkUpdates?.entityBatchUpdate?.mutations);
if (on_response_received_endpoints_memo) {
applyCommentsMutations(on_response_received_endpoints_memo, data.frameworkUpdates?.entityBatchUpdate?.mutations);
}
const continuation = data.continuation ? parseC(data.continuation) : null;
if (continuation) {
parsed_data.continuation = continuation;
@@ -683,3 +689,28 @@ export function applyMutations(memo: Memo, mutations: RawNode[]) {
}
}
}
export function applyCommentsMutations(memo: Memo, mutations: RawNode[]) {
const comment_view_items = memo.getType(CommentView);
if (comment_view_items.length > 0) {
if (!mutations) {
ERROR_HANDLER({
error_type: 'mutation_data_missing',
classname: 'CommentView'
});
}
for (const comment_view of comment_view_items) {
const comment_mutation = mutations
.find((mutation) => mutation.payload?.commentEntityPayload?.key === comment_view.keys.comment)
?.payload?.commentEntityPayload;
const toolbar_state_mutation = mutations
.find((mutation) => mutation.payload?.engagementToolbarStateEntityPayload?.key === comment_view.keys.toolbar_state)
?.payload?.engagementToolbarStateEntityPayload;
comment_view.applyMutations(comment_mutation, toolbar_state_mutation);
}
}
}