mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-07-02 21:52:48 +00:00
feat(parser): Support CommentView nodes (#614)
This commit is contained in:
@@ -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;
|
||||
}));
|
||||
|
||||
88
src/parser/classes/comments/CommentView.ts
Normal file
88
src/parser/classes/comments/CommentView.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user