diff --git a/examples/livechat/README.md b/examples/livechat/README.md
index 7b8ab796..213a6ab6 100644
--- a/examples/livechat/README.md
+++ b/examples/livechat/README.md
@@ -1,16 +1,16 @@
## Live Chat
-The library's Live Chat parser and poller were heavily based on YouTube's original compiled code, this makes it behave in a similar if not identical way to YouTube's Live Chat. Here you can do all sorts of funny things, ex; track messages, donations, polls, and much more.
+Represents a livestream chat.
## Usage
-Before fetching a Live Chat, you have to retrieve the target livestream's info:
+Before fetching a live chat, you have to retrieve the target livestream's info:
```js
const info = await yt.getInfo('video_id');
```
-Then you may request a Live Chat instance:
+Then you may request a live chat instance:
```js
const livechat = await info.getLiveChat();
```
@@ -21,6 +21,7 @@ const livechat = await info.getLiveChat();
* [.ev](#ev) ⇒ `EventEmitter`
* [.start](#start) ⇒ `function`
* [.stop](#stop) ⇒ `function`
+ * [.applyFilter](#applyfilter) ⇒ `function`
* [.getItemMenu](#getitemmenu) ⇒ `function`
* [.sendMessage](#sendmessage) ⇒ `function`
@@ -31,6 +32,8 @@ Live Chat's EventEmitter.
**Events:**
- `start`
+
+ Fired when the live chat is started.
Arguments:
| Type | Description |
@@ -38,18 +41,35 @@ Live Chat's EventEmitter.
| `LiveChatContinuation` | Initial chat data, actions, info, etc. |
- `chat-update`
-
+
+ Fired when a new chat action is received.
+
Arguments:
| Type | Description |
| --- | --- |
- | `ChatAction` | Chat Action |
+ | `ChatAction` | Chat action |
- `metadata-update`
+
+ Fired when the livestream's metadata is updated.
Arguments:
| Type | Description |
| --- | --- |
- | `LiveMetadata` | LiveStream Metadata |
+ | `LiveMetadata` | Livestream metadata |
+
+- `error`
+
+ Fired when an error occurs.
+
+ Arguments:
+ | Type | Description |
+ | --- | --- |
+ | `Error` | Details about the error |
+
+- `end`
+
+ Fired when the livestream ends.
### start()
@@ -59,6 +79,15 @@ Starts the Live Chat.
### stop()
Stops the Live Chat.
+
+### applyFilter(filter)
+
+Applies given filter to the live chat.
+
+| Param | Type | Description |
+| --- | --- | --- |
+| filter | `string` | Can be `TOP_CHAT` or `LIVE_CHAT` |
+
### getItemMenu(item)
Retrieves given chat item's menu.
diff --git a/examples/livechat/index.ts b/examples/livechat/index.ts
index b6fa96c3..bbe5a325 100644
--- a/examples/livechat/index.ts
+++ b/examples/livechat/index.ts
@@ -1,25 +1,38 @@
import { Innertube, UniversalCache, YTNodes } from 'youtubei.js';
-
import { LiveChatContinuation } from 'youtubei.js/dist/src/parser';
import { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/LiveChat';
(async () => {
- const yt = await Innertube.create({ cache: new UniversalCache() });
+ const yt = await Innertube.create({ cache: new UniversalCache(), generate_session_locally: true });
-
- const search = await yt.search('Lofi girl live');
+ const search = await yt.search('lofi hip hop radio - beats to relax/study to');
const info = await yt.getInfo(search.videos[0].as(YTNodes.Video).id);
const livechat = info.getLiveChat();
livechat.on('start', (initial_data: LiveChatContinuation) => {
/**
- * Initial info is what you see when you first open a Live Chat — this is; inital actions (pinned messages, top donations..), account's info and so on.
+ * Initial info is what you see when you first open a a live chat — this is; initial actions (pinned messages, top donations..), account's info and so forth.
*/
+ console.info(`Hey ${initial_data.viewer_name || 'Guest'}, welcome to Live Chat!`);
- console.info(`Hey ${initial_data.viewer_name || 'N/A'}, welcome to Live Chat!`);
+ const pinned_action = initial_data.actions.firstOfType(YTNodes.AddBannerToLiveChatCommand);
+
+ if (pinned_action) {
+ if (pinned_action.banner?.contents?.is(YTNodes.LiveChatTextMessage)) {
+ console.info(
+ '\n', 'Pinned message:\n',
+ pinned_action.banner.contents.author?.name.toString(), '-', pinned_action?.banner.contents.message.toString(),
+ '\n'
+ );
+ }
+ }
});
+ livechat.on('error', (error: Error) => console.info('Live chat error:', error));
+
+ livechat.on('end', () => console.info('This live stream has ended.'));
+
livechat.on('chat-update', (action: ChatAction) => {
/**
* An action represents what is being added to
@@ -43,24 +56,42 @@ import { ChatAction, LiveMetadata } from 'youtubei.js/dist/src/parser/youtube/Li
switch (item.type) {
case 'LiveChatTextMessage':
console.info(
+ `${item.as(YTNodes.LiveChatTextMessage).author?.is_moderator ? '[MOD]' : ''}`,
`${hours} - ${item.as(YTNodes.LiveChatTextMessage).author?.name.toString()}:\n` +
`${item.as(YTNodes.LiveChatTextMessage).message.toString()}\n`
);
break;
case 'LiveChatPaidMessage':
console.info(
+ `${item.as(YTNodes.LiveChatPaidMessage).author?.is_moderator ? '[MOD]' : ''}`,
`${hours} - ${item.as(YTNodes.LiveChatPaidMessage).author.name.toString()}:\n` +
+ `${item.as(YTNodes.LiveChatPaidMessage).message.toString()}\n`,
`${item.as(YTNodes.LiveChatPaidMessage).purchase_amount}\n`
);
break;
+ case 'LiveChatPaidSticker':
+ console.info(
+ `${item.as(YTNodes.LiveChatPaidSticker).author?.is_moderator ? '[MOD]' : ''}`,
+ `${hours} - ${item.as(YTNodes.LiveChatPaidSticker).author.name.toString()}:\n` +
+ `${item.as(YTNodes.LiveChatPaidSticker).purchase_amount}\n`
+ );
+ break;
default:
console.debug(action);
break;
}
}
- if (action.is(YTNodes.MarkChatItemAsDeletedAction)) {
- console.warn(`Message ${action.target_item_id} just got deleted and should be replaced with ${action.deleted_state_message.toString()}!`, '\n');
+ if (action.is(YTNodes.AddBannerToLiveChatCommand)) {
+ console.info('Message pinned:', action.banner?.contents);
+ }
+
+ if (action.is(YTNodes.RemoveBannerForLiveChatCommand)) {
+ console.info(`Message with action id ${action.target_action_id} was unpinned.`);
+ }
+
+ if (action.is(YTNodes.RemoveChatItemAction)) {
+ console.warn(`Message with action id ${action.target_item_id} just got deleted!`, '\n');
}
});
diff --git a/src/parser/classes/LiveChatHeader.ts b/src/parser/classes/LiveChatHeader.ts
index d1cd1613..06547ef9 100644
--- a/src/parser/classes/LiveChatHeader.ts
+++ b/src/parser/classes/LiveChatHeader.ts
@@ -1,18 +1,21 @@
import Parser from '../index';
+import type Menu from './menus/Menu';
+import type Button from './Button';
+import type SortFilterSubMenu from './SortFilterSubMenu';
import { YTNode } from '../helpers';
class LiveChatHeader extends YTNode {
static type = 'LiveChatHeader';
- overflow_menu;
- collapse_button;
- view_selector;
+ overflow_menu: Menu | null;
+ collapse_button: Button | null;
+ view_selector: SortFilterSubMenu | null;
constructor(data: any) {
super();
- this.overflow_menu = Parser.parse(data.overflowMenu);
- this.collapse_button = Parser.parse(data.collapseButton);
- this.view_selector = Parser.parse(data.viewSelector);
+ this.overflow_menu = Parser.parseItem
(data.overflowMenu);
+ this.collapse_button = Parser.parseItem(data.collapseButton);
+ this.view_selector = Parser.parseItem(data.viewSelector);
}
}
diff --git a/src/parser/classes/LiveChatItemList.ts b/src/parser/classes/LiveChatItemList.ts
index 75c3ce6d..af69a83f 100644
--- a/src/parser/classes/LiveChatItemList.ts
+++ b/src/parser/classes/LiveChatItemList.ts
@@ -1,16 +1,17 @@
import Parser from '../index';
import { YTNode } from '../helpers';
+import type Button from './Button';
class LiveChatItemList extends YTNode {
static type = 'LiveChatItemList';
max_items_to_display: string;
- more_comments_below_button;
+ more_comments_below_button: Button | null;
constructor(data: any) {
super();
this.max_items_to_display = data.maxItemsToDisplay;
- this.more_comments_below_button = Parser.parse(data.moreCommentsBelowButton);
+ this.more_comments_below_button = Parser.parseItem(data.moreCommentsBelowButton);
}
}
diff --git a/src/parser/classes/LiveChatMessageInput.ts b/src/parser/classes/LiveChatMessageInput.ts
index 0708da59..cc055ecd 100644
--- a/src/parser/classes/LiveChatMessageInput.ts
+++ b/src/parser/classes/LiveChatMessageInput.ts
@@ -1,6 +1,7 @@
import Text from './misc/Text';
import Parser from '../index';
import Thumbnail from './misc/Thumbnail';
+import type Button from './Button';
import { YTNode } from '../helpers';
class LiveChatMessageInput extends YTNode {
@@ -8,14 +9,14 @@ class LiveChatMessageInput extends YTNode {
author_name: Text;
author_photo: Thumbnail[];
- send_button;
+ send_button: Button | null;
target_id: string;
constructor(data: any) {
super();
this.author_name = new Text(data.authorName);
this.author_photo = Thumbnail.fromResponse(data.authorPhoto);
- this.send_button = Parser.parse(data.sendButton);
+ this.send_button = Parser.parseItem(data.sendButton);
this.target_id = data.targetId;
}
}
diff --git a/src/parser/classes/LiveChatParticipantsList.ts b/src/parser/classes/LiveChatParticipantsList.ts
index f191169e..965746c5 100644
--- a/src/parser/classes/LiveChatParticipantsList.ts
+++ b/src/parser/classes/LiveChatParticipantsList.ts
@@ -1,17 +1,18 @@
import Parser from '../index';
import Text from './misc/Text';
-import { YTNode } from '../helpers';
+import { ObservedArray, YTNode } from '../helpers';
+import type LiveChatParticipant from './LiveChatParticipant';
class LiveChatParticipantsList extends YTNode {
static type = 'LiveChatParticipantsList';
title: Text;
- participants;
+ participants: ObservedArray;
constructor(data: any) {
super();
this.title = new Text(data.title);
- this.participants = Parser.parse(data.participants);
+ this.participants = Parser.parseArray(data.participants);
}
}
diff --git a/src/parser/classes/SortFilterSubMenu.ts b/src/parser/classes/SortFilterSubMenu.ts
index 6f987204..1ad2a8c8 100644
--- a/src/parser/classes/SortFilterSubMenu.ts
+++ b/src/parser/classes/SortFilterSubMenu.ts
@@ -12,7 +12,7 @@ class SortFilterSubMenu extends YTNode {
sub_menu_items?: {
title: string;
selected: boolean;
- continuation: string | null;
+ continuation: string;
endpoint: NavigationEndpoint;
subtitle: string | null;
}[];
@@ -40,7 +40,7 @@ class SortFilterSubMenu extends YTNode {
this.sub_menu_items = data.subMenuItems.map((item: any) => ({
title: item.title,
selected: item.selected,
- continuation: item.continuation?.reloadContinuationData?.continuation || null,
+ continuation: item.continuation?.reloadContinuationData?.continuation,
endpoint: new NavigationEndpoint(item.serviceEndpoint),
subtitle: item.subtitle || null
}));
diff --git a/src/parser/classes/UpsellDialog.ts b/src/parser/classes/UpsellDialog.ts
new file mode 100644
index 00000000..77c3c58c
--- /dev/null
+++ b/src/parser/classes/UpsellDialog.ts
@@ -0,0 +1,25 @@
+import Parser from '..';
+import { YTNode } from '../helpers';
+import type Button from './Button';
+import Text from './misc/Text';
+
+class UpsellDialog extends YTNode {
+ static type = 'UpsellDialog';
+
+ message_title: Text;
+ message_text: Text;
+ action_button: Button | null;
+ dismiss_button: Button | null;
+ is_visible: boolean;
+
+ constructor(data: any) {
+ super();
+ this.message_title = new Text(data.dialogMessageTitle);
+ this.message_text = new Text(data.dialogMessageText);
+ this.action_button = Parser.parseItem(data.actionButton);
+ this.dismiss_button = Parser.parseItem(data.dismissButton);
+ this.is_visible = data.isVisible;
+ }
+}
+
+export default UpsellDialog;
\ No newline at end of file
diff --git a/src/parser/classes/livechat/AddBannerToLiveChatCommand.ts b/src/parser/classes/livechat/AddBannerToLiveChatCommand.ts
index ce8c9142..02e1a392 100644
--- a/src/parser/classes/livechat/AddBannerToLiveChatCommand.ts
+++ b/src/parser/classes/livechat/AddBannerToLiveChatCommand.ts
@@ -1,14 +1,15 @@
import Parser from '../../index';
import { YTNode } from '../../helpers';
+import type LiveChatBanner from './items/LiveChatBanner';
class AddBannerToLiveChatCommand extends YTNode {
static type = 'AddBannerToLiveChatCommand';
- banner;
+ banner: LiveChatBanner | null;
constructor(data: any) {
super();
- this.banner = Parser.parse(data.bannerRenderer);
+ this.banner = Parser.parseItem(data.bannerRenderer);
}
}
diff --git a/src/parser/classes/livechat/AddLiveChatTickerItemAction.ts b/src/parser/classes/livechat/AddLiveChatTickerItemAction.ts
index 76765d56..e5ee74ea 100644
--- a/src/parser/classes/livechat/AddLiveChatTickerItemAction.ts
+++ b/src/parser/classes/livechat/AddLiveChatTickerItemAction.ts
@@ -5,7 +5,7 @@ class AddLiveChatTickerItemAction extends YTNode {
static type = 'AddLiveChatTickerItemAction';
item;
- duration_sec;
+ duration_sec: string; // TODO: check this assumption
constructor(data: any) {
super();
diff --git a/src/parser/classes/livechat/DimChatItemAction.ts b/src/parser/classes/livechat/DimChatItemAction.ts
new file mode 100644
index 00000000..ab77e9e3
--- /dev/null
+++ b/src/parser/classes/livechat/DimChatItemAction.ts
@@ -0,0 +1,14 @@
+import { YTNode } from '../../helpers';
+
+class DimChatItemAction extends YTNode {
+ static type = 'DimChatItemAction';
+
+ client_assigned_id: string;
+
+ constructor(data: any) {
+ super();
+ this.client_assigned_id = data.clientAssignedId;
+ }
+}
+
+export default DimChatItemAction;
\ No newline at end of file
diff --git a/src/parser/classes/livechat/ReplaceChatItemAction.ts b/src/parser/classes/livechat/ReplaceChatItemAction.ts
index e72e5bf3..9a354781 100644
--- a/src/parser/classes/livechat/ReplaceChatItemAction.ts
+++ b/src/parser/classes/livechat/ReplaceChatItemAction.ts
@@ -10,7 +10,7 @@ class ReplaceChatItemAction extends YTNode {
constructor(data: any) {
super();
this.target_item_id = data.targetItemId;
- this.replacement_item = Parser.parse(data.replacementItem);
+ this.replacement_item = Parser.parseItem(data.replacementItem);
}
}
diff --git a/src/parser/classes/livechat/ReplayChatItemAction.ts b/src/parser/classes/livechat/ReplayChatItemAction.ts
index 6320542c..3fac9898 100644
--- a/src/parser/classes/livechat/ReplayChatItemAction.ts
+++ b/src/parser/classes/livechat/ReplayChatItemAction.ts
@@ -9,10 +9,10 @@ class ReplayChatItemAction extends YTNode {
constructor(data: any) {
super();
- this.actions = Parser.parse(data.actions?.map((action: any) => {
+ this.actions = Parser.parseArray(data.actions?.map((action: any) => {
delete action.clickTrackingParams;
return action;
- })) || [];
+ }));
this.video_offset_time_msec = data.videoOffsetTimeMsec;
}
}
diff --git a/src/parser/classes/livechat/ShowLiveChatActionPanelAction.ts b/src/parser/classes/livechat/ShowLiveChatActionPanelAction.ts
index 00261cc2..c4fd781d 100644
--- a/src/parser/classes/livechat/ShowLiveChatActionPanelAction.ts
+++ b/src/parser/classes/livechat/ShowLiveChatActionPanelAction.ts
@@ -1,14 +1,15 @@
import Parser from '../../index';
import { YTNode } from '../../helpers';
+import LiveChatActionPanel from './LiveChatActionPanel';
class ShowLiveChatActionPanelAction extends YTNode {
static type = 'ShowLiveChatActionPanelAction';
- panel_to_show;
+ panel_to_show: LiveChatActionPanel | null;
constructor(data: any) {
super();
- this.panel_to_show = Parser.parse(data.panelToShow);
+ this.panel_to_show = Parser.parseItem(data.panelToShow, LiveChatActionPanel);
}
}
diff --git a/src/parser/classes/livechat/ShowLiveChatDialogAction.ts b/src/parser/classes/livechat/ShowLiveChatDialogAction.ts
new file mode 100644
index 00000000..f6f03f6a
--- /dev/null
+++ b/src/parser/classes/livechat/ShowLiveChatDialogAction.ts
@@ -0,0 +1,15 @@
+import Parser from '../..';
+import { YTNode } from '../../helpers';
+
+class ShowLiveChatDialogAction extends YTNode {
+ static type = 'ShowLiveChatDialogAction';
+
+ dialog;
+
+ constructor(data: any) {
+ super();
+ this.dialog = Parser.parseItem(data.dialog);
+ }
+}
+
+export default ShowLiveChatDialogAction;
\ No newline at end of file
diff --git a/src/parser/classes/livechat/ShowLiveChatTooltipCommand.ts b/src/parser/classes/livechat/ShowLiveChatTooltipCommand.ts
index 7ed83934..09414e77 100644
--- a/src/parser/classes/livechat/ShowLiveChatTooltipCommand.ts
+++ b/src/parser/classes/livechat/ShowLiveChatTooltipCommand.ts
@@ -8,7 +8,7 @@ class ShowLiveChatTooltipCommand extends YTNode {
constructor(data: any) {
super();
- this.tooltip = Parser.parse(data.tooltip);
+ this.tooltip = Parser.parseItem(data.tooltip);
}
}
diff --git a/src/parser/classes/livechat/UpdateLiveChatPollAction.ts b/src/parser/classes/livechat/UpdateLiveChatPollAction.ts
index bae55e2d..8c69d10f 100644
--- a/src/parser/classes/livechat/UpdateLiveChatPollAction.ts
+++ b/src/parser/classes/livechat/UpdateLiveChatPollAction.ts
@@ -8,7 +8,7 @@ class UpdateLiveChatPollAction extends YTNode {
constructor(data: any) {
super();
- this.poll_to_update = Parser.parse(data.pollToUpdate);
+ this.poll_to_update = Parser.parseItem(data.pollToUpdate);
}
}
diff --git a/src/parser/classes/livechat/items/LiveChatAutoModMessage.ts b/src/parser/classes/livechat/items/LiveChatAutoModMessage.ts
index 0861b891..58f34a7b 100644
--- a/src/parser/classes/livechat/items/LiveChatAutoModMessage.ts
+++ b/src/parser/classes/livechat/items/LiveChatAutoModMessage.ts
@@ -1,8 +1,8 @@
-import Text from '../../misc/Text';
-import Parser from '../../../index';
import { ObservedArray, YTNode } from '../../../helpers';
-import NavigationEndpoint from '../../NavigationEndpoint';
+import Parser from '../../../index';
import Button from '../../Button';
+import Text from '../../misc/Text';
+import NavigationEndpoint from '../../NavigationEndpoint';
class LiveChatAutoModMessage extends YTNode {
static type = 'LiveChatAutoModMessage';
@@ -17,10 +17,9 @@ class LiveChatAutoModMessage extends YTNode {
constructor(data: any) {
super();
-
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
this.moderation_buttons = Parser.parseArray(data.moderationButtons, [ Button ]);
- this.auto_moderated_item = Parser.parse(data.autoModeratedItem);
+ this.auto_moderated_item = Parser.parseItem(data.autoModeratedItem);
this.header_text = new Text(data.headerText);
this.timestamp = Math.floor(parseInt(data.timestampUsec) / 1000);
this.id = data.id;
diff --git a/src/parser/classes/livechat/items/LiveChatBanner.ts b/src/parser/classes/livechat/items/LiveChatBanner.ts
index 78c7f005..4c952ed0 100644
--- a/src/parser/classes/livechat/items/LiveChatBanner.ts
+++ b/src/parser/classes/livechat/items/LiveChatBanner.ts
@@ -1,10 +1,11 @@
-import Parser from '../../../index';
import { YTNode } from '../../../helpers';
+import Parser from '../../../index';
+import type LiveChatBannerHeader from './LiveChatBannerHeader';
class LiveChatBanner extends YTNode {
static type = 'LiveChatBanner';
- header;
+ header: LiveChatBannerHeader | null;
contents;
action_id: string;
viewer_is_creator: boolean;
@@ -14,9 +15,8 @@ class LiveChatBanner extends YTNode {
constructor(data: any) {
super();
-
- this.header = Parser.parse(data.header);
- this.contents = Parser.parse(data.contents);
+ this.header = Parser.parseItem(data.header);
+ this.contents = Parser.parseItem(data.contents);
this.action_id = data.actionId;
this.viewer_is_creator = data.viewerIsCreator;
this.target_id = data.targetId;
diff --git a/src/parser/classes/livechat/items/LiveChatBannerHeader.ts b/src/parser/classes/livechat/items/LiveChatBannerHeader.ts
index f5026ea4..5fcbcbe9 100644
--- a/src/parser/classes/livechat/items/LiveChatBannerHeader.ts
+++ b/src/parser/classes/livechat/items/LiveChatBannerHeader.ts
@@ -1,19 +1,20 @@
-import Parser from '../../../index';
-import Text from '../../misc/Text';
import { YTNode } from '../../../helpers';
+import Parser from '../../../index';
+import type Button from '../../Button';
+import Text from '../../misc/Text';
class LiveChatBannerHeader extends YTNode {
static type = 'LiveChatBannerHeader';
text: string;
icon_type: string;
- context_menu_button;
+ context_menu_button: Button | null;
constructor(data: any) {
super();
this.text = new Text(data.text).toString();
- this.icon_type = data.icon.iconType;
- this.context_menu_button = Parser.parse(data.contextMenuButton);
+ this.icon_type = data.icon?.iconType;
+ this.context_menu_button = Parser.parseItem(data.contextMenuButton);
}
}
diff --git a/src/parser/classes/livechat/items/LiveChatBannerPoll.ts b/src/parser/classes/livechat/items/LiveChatBannerPoll.ts
index 3f934fd6..8163d4e6 100644
--- a/src/parser/classes/livechat/items/LiveChatBannerPoll.ts
+++ b/src/parser/classes/livechat/items/LiveChatBannerPoll.ts
@@ -1,7 +1,7 @@
+import { YTNode } from '../../../helpers';
import Parser from '../../../index';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
-import { YTNode } from '../../../helpers';
class LiveChatBannerPoll extends YTNode {
static type = 'LiveChatBannerPoll';
@@ -29,7 +29,7 @@ class LiveChatBannerPoll extends YTNode {
this.collapsed_state_entity_key = data.collapsedStateEntityKey;
this.live_chat_poll_state_entity_key = data.liveChatPollStateEntityKey;
- this.context_menu_button = Parser.parse(data.contextMenuButton);
+ this.context_menu_button = Parser.parseItem(data.contextMenuButton);
}
}
diff --git a/src/parser/classes/livechat/items/LiveChatMembershipItem.ts b/src/parser/classes/livechat/items/LiveChatMembershipItem.ts
index 15715684..7add4df3 100644
--- a/src/parser/classes/livechat/items/LiveChatMembershipItem.ts
+++ b/src/parser/classes/livechat/items/LiveChatMembershipItem.ts
@@ -1,8 +1,10 @@
+import { observe, ObservedArray, YTNode } from '../../../helpers';
import Parser from '../../../index';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
import NavigationEndpoint from '../../NavigationEndpoint';
-import { YTNode } from '../../../helpers';
class LiveChatMembershipItem extends YTNode {
static type = 'LiveChatMembershipItem';
@@ -15,7 +17,10 @@ class LiveChatMembershipItem extends YTNode {
id: string;
name: Text;
thumbnails: Thumbnail[];
- badges: any;
+ badges: ObservedArray;
+ is_moderator: boolean | null;
+ is_verified: boolean | null;
+ is_verified_artist: boolean | null;
};
menu_endpoint: NavigationEndpoint;
@@ -30,9 +35,19 @@ class LiveChatMembershipItem extends YTNode {
id: data.authorExternalChannelId,
name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
- badges: Parser.parse(data.authorBadges)
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
+ is_moderator: null,
+ is_verified: null,
+ is_verified_artist: null
};
+ const badges = Parser.parseArray(data.authorBadges);
+
+ this.author.badges = badges;
+ this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
+ this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
+ this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
+
this.menu_endpoint = new NavigationEndpoint(data.contextMenuEndpoint);
}
}
diff --git a/src/parser/classes/livechat/items/LiveChatPaidMessage.ts b/src/parser/classes/livechat/items/LiveChatPaidMessage.ts
index 14cc4b08..bbb4b52b 100644
--- a/src/parser/classes/livechat/items/LiveChatPaidMessage.ts
+++ b/src/parser/classes/livechat/items/LiveChatPaidMessage.ts
@@ -1,10 +1,10 @@
+import { observe, ObservedArray, YTNode } from '../../../helpers';
+import Parser from '../../../index';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
import NavigationEndpoint from '../../NavigationEndpoint';
-import MetadataBadge from '../../MetadataBadge';
-import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
-import Parser from '../../../index';
-import { YTNode } from '../../../helpers';
class LiveChatPaidMessage extends YTNode {
static type = 'LiveChatPaidMessage';
@@ -15,7 +15,7 @@ class LiveChatPaidMessage extends YTNode {
id: string;
name: Text;
thumbnails: Thumbnail[];
- badges: LiveChatAuthorBadge[] | MetadataBadge[];
+ badges: ObservedArray;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
@@ -39,7 +39,7 @@ class LiveChatPaidMessage extends YTNode {
id: data.authorExternalChannelId,
name: new Text(data.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
- badges: Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]),
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
@@ -48,9 +48,9 @@ class LiveChatPaidMessage extends YTNode {
const badges = Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
- this.author.is_moderator = badges?.some((badge: any) => badge.icon_type == 'MODERATOR') || null;
- this.author.is_verified = badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') || null;
- this.author.is_verified_artist = badges?.some((badge: any) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') || null;
+ this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
+ this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
+ this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
this.header_background_color = data.headerBackgroundColor;
this.header_text_color = data.headerTextColor;
diff --git a/src/parser/classes/livechat/items/LiveChatPaidSticker.ts b/src/parser/classes/livechat/items/LiveChatPaidSticker.ts
index b59a9a04..032a0ccd 100644
--- a/src/parser/classes/livechat/items/LiveChatPaidSticker.ts
+++ b/src/parser/classes/livechat/items/LiveChatPaidSticker.ts
@@ -1,8 +1,10 @@
+import { observe, ObservedArray, YTNode } from '../../../helpers';
import Parser from '../../../index';
-import NavigationEndpoint from '../../NavigationEndpoint';
-import Thumbnail from '../../misc/Thumbnail';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
-import { YTNode } from '../../../helpers';
+import Thumbnail from '../../misc/Thumbnail';
+import NavigationEndpoint from '../../NavigationEndpoint';
class LiveChatPaidSticker extends YTNode {
static type = 'LiveChatPaidSticker';
@@ -13,7 +15,10 @@ class LiveChatPaidSticker extends YTNode {
id: string;
name: Text;
thumbnails: Thumbnail[];
- badges: any;
+ badges: ObservedArray;
+ is_moderator: boolean | null;
+ is_verified: boolean | null;
+ is_verified_artist: boolean | null;
};
money_chip_background_color: number;
@@ -34,9 +39,19 @@ class LiveChatPaidSticker extends YTNode {
id: data.authorExternalChannelId,
name: new Text(data.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
- badges: Parser.parse(data.authorBadges)
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
+ is_moderator: null,
+ is_verified: null,
+ is_verified_artist: null
};
+ const badges = Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
+
+ this.author.badges = badges;
+ this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
+ this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
+ this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
+
this.money_chip_background_color = data.moneyChipBackgroundColor;
this.money_chip_text_color = data.moneyChipTextColor;
this.background_color = data.backgroundColor;
diff --git a/src/parser/classes/livechat/items/LiveChatProductItem.ts b/src/parser/classes/livechat/items/LiveChatProductItem.ts
index e5849d9c..c271e9e5 100644
--- a/src/parser/classes/livechat/items/LiveChatProductItem.ts
+++ b/src/parser/classes/livechat/items/LiveChatProductItem.ts
@@ -32,12 +32,12 @@ class LiveChatProductItem extends YTNode {
this.price = data.price;
this.vendor_name = data.vendorName;
this.from_vendor_text = data.fromVendorText;
- this.information_button = Parser.parse(data.informationButton);
+ this.information_button = Parser.parseItem(data.informationButton);
this.endpoint = new NavigationEndpoint(data.onClickCommand);
this.creator_message = data.creatorMessage;
this.creator_name = data.creatorName;
this.author_photo = Thumbnail.fromResponse(data.authorPhoto);
- this.information_dialog = Parser.parse(data.informationDialog);
+ this.information_dialog = Parser.parseItem(data.informationDialog);
this.is_verified = data.isVerified;
this.creator_custom_message = new Text(data.creatorCustomMessage);
}
diff --git a/src/parser/classes/livechat/items/LiveChatTextMessage.ts b/src/parser/classes/livechat/items/LiveChatTextMessage.ts
index 6e874443..a5deb742 100644
--- a/src/parser/classes/livechat/items/LiveChatTextMessage.ts
+++ b/src/parser/classes/livechat/items/LiveChatTextMessage.ts
@@ -1,12 +1,11 @@
+import { observe, ObservedArray, YTNode } from '../../../helpers';
+import Parser from '../../../index';
+import Button from '../../Button';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
import NavigationEndpoint from '../../NavigationEndpoint';
-import MetadataBadge from '../../MetadataBadge';
-import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
-import Parser from '../../../index';
-
-import { ObservedArray, YTNode } from '../../../helpers';
-import Button from '../../Button';
class LiveChatTextMessage extends YTNode {
static type = 'LiveChatTextMessage';
@@ -16,7 +15,7 @@ class LiveChatTextMessage extends YTNode {
id: string;
name: Text;
thumbnails: Thumbnail[];
- badges: LiveChatAuthorBadge[] | MetadataBadge[];
+ badges: ObservedArray;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
@@ -35,7 +34,7 @@ class LiveChatTextMessage extends YTNode {
id: data.authorExternalChannelId,
name: new Text(data.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
- badges: [] as LiveChatAuthorBadge[] | [] as MetadataBadge[],
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
diff --git a/src/parser/classes/livechat/items/LiveChatTickerPaidMessageItem.ts b/src/parser/classes/livechat/items/LiveChatTickerPaidMessageItem.ts
index 0a0133d1..9e2d4269 100644
--- a/src/parser/classes/livechat/items/LiveChatTickerPaidMessageItem.ts
+++ b/src/parser/classes/livechat/items/LiveChatTickerPaidMessageItem.ts
@@ -1,19 +1,20 @@
+import Parser from '../../../index';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
import NavigationEndpoint from '../../NavigationEndpoint';
-import MetadataBadge from '../../MetadataBadge';
-import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
-import Parser from '../../../index';
-import { YTNode } from '../../../helpers';
+import { observe, ObservedArray, YTNode } from '../../../helpers';
class LiveChatTickerPaidMessageItem extends YTNode {
static type = 'LiveChatTickerPaidMessageItem';
author: {
id: string;
+ name: Text;
thumbnails: Thumbnail[];
- badges: LiveChatAuthorBadge[] | MetadataBadge[];
+ badges: ObservedArray;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
@@ -31,8 +32,9 @@ class LiveChatTickerPaidMessageItem extends YTNode {
this.author = {
id: data.authorExternalChannelId,
+ name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
- badges: Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]),
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
@@ -41,13 +43,14 @@ class LiveChatTickerPaidMessageItem extends YTNode {
const badges = Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
- this.author.is_moderator = badges?.some((badge) => badge.icon_type == 'MODERATOR') || null;
- this.author.is_verified = badges?.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') || null;
- this.author.is_verified_artist = badges?.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') || null;
+ this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
+ this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
+ this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
+
this.amount = new Text(data.amount);
this.duration_sec = data.durationSec;
this.full_duration_sec = data.fullDurationSec;
- this.show_item = Parser.parse(data.showItemEndpoint.showLiveChatItemEndpoint.renderer);
+ this.show_item = Parser.parse(data.showItemEndpoint?.showLiveChatItemEndpoint?.renderer);
this.show_item_endpoint = new NavigationEndpoint(data.showItemEndpoint);
this.id = data.id;
}
diff --git a/src/parser/classes/livechat/items/LiveChatTickerPaidStickerItem.ts b/src/parser/classes/livechat/items/LiveChatTickerPaidStickerItem.ts
index c580200e..255d498c 100644
--- a/src/parser/classes/livechat/items/LiveChatTickerPaidStickerItem.ts
+++ b/src/parser/classes/livechat/items/LiveChatTickerPaidStickerItem.ts
@@ -1,19 +1,20 @@
+import Parser from '../../../index';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
import NavigationEndpoint from '../../NavigationEndpoint';
-import MetadataBadge from '../../MetadataBadge';
-import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
-import Parser from '../../../index';
-import { YTNode } from '../../../helpers';
+import { observe, ObservedArray, YTNode } from '../../../helpers';
class LiveChatTickerPaidStickerItem extends YTNode {
static type = 'LiveChatTickerPaidStickerItem';
author: {
id: string;
+ name: Text;
thumbnails: Thumbnail[];
- badges: LiveChatAuthorBadge[] | MetadataBadge[];
+ badges: ObservedArray;
is_moderator: boolean | null;
is_verified: boolean | null;
is_verified_artist: boolean | null;
@@ -31,8 +32,9 @@ class LiveChatTickerPaidStickerItem extends YTNode {
this.author = {
id: data.authorExternalChannelId,
+ name: new Text(data?.authorName),
thumbnails: Thumbnail.fromResponse(data.authorPhoto),
- badges: Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]),
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
is_moderator: null,
is_verified: null,
is_verified_artist: null
@@ -41,13 +43,14 @@ class LiveChatTickerPaidStickerItem extends YTNode {
const badges = Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
this.author.badges = badges;
- this.author.is_moderator = badges?.some((badge) => badge.icon_type == 'MODERATOR') || null;
- this.author.is_verified = badges?.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') || null;
- this.author.is_verified_artist = badges?.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') || null;
+ this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
+ this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
+ this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
+
this.amount = new Text(data.amount);
this.duration_sec = data.durationSec;
this.full_duration_sec = data.fullDurationSec;
- this.show_item = Parser.parse(data.showItemEndpoint.showLiveChatItemEndpoint.renderer);
+ this.show_item = Parser.parseItem(data.showItemEndpoint?.showLiveChatItemEndpoint?.renderer);
this.show_item_endpoint = new NavigationEndpoint(data.showItemEndpoint);
this.id = data.id;
}
diff --git a/src/parser/classes/livechat/items/LiveChatTickerSponsorItem.ts b/src/parser/classes/livechat/items/LiveChatTickerSponsorItem.ts
index d4300ebb..e5dab63b 100644
--- a/src/parser/classes/livechat/items/LiveChatTickerSponsorItem.ts
+++ b/src/parser/classes/livechat/items/LiveChatTickerSponsorItem.ts
@@ -1,16 +1,23 @@
+import Parser from '../../..';
+import { observe, ObservedArray, YTNode } from '../../../helpers';
+import LiveChatAuthorBadge from '../../LiveChatAuthorBadge';
+import MetadataBadge from '../../MetadataBadge';
import Text from '../../misc/Text';
import Thumbnail from '../../misc/Thumbnail';
-import { YTNode } from '../../../helpers';
class LiveChatTickerSponsorItem extends YTNode {
static type = 'LiveChatTickerSponsorItem';
id: string;
- detail_text: string;
+ detail: Text;
author: {
id: string;
name: Text;
thumbnails: Thumbnail[];
+ badges: ObservedArray;
+ is_moderator: boolean | null;
+ is_verified: boolean | null;
+ is_verified_artist: boolean | null;
};
duration_sec: string;
@@ -18,14 +25,25 @@ class LiveChatTickerSponsorItem extends YTNode {
constructor(data: any) {
super();
this.id = data.id;
- this.detail_text = new Text(data.detailText).toString();
+ this.detail = new Text(data.detailText);
this.author = {
id: data.authorExternalChannelId,
name: new Text(data?.authorName),
- thumbnails: Thumbnail.fromResponse(data.sponsorPhoto)
+ thumbnails: Thumbnail.fromResponse(data.sponsorPhoto),
+ badges: observe([]).as(LiveChatAuthorBadge, MetadataBadge),
+ is_moderator: null,
+ is_verified: null,
+ is_verified_artist: null
};
+ const badges = Parser.parseArray(data.authorBadges, [ MetadataBadge, LiveChatAuthorBadge ]);
+
+ this.author.badges = badges;
+ this.author.is_moderator = badges ? badges.some((badge) => badge.icon_type == 'MODERATOR') : null;
+ this.author.is_verified = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED') : null;
+ this.author.is_verified_artist = badges ? badges.some((badge) => badge.style == 'BADGE_STYLE_TYPE_VERIFIED_ARTIST') : null;
+
this.duration_sec = data.durationSec;
// TODO: finish this
}
diff --git a/src/parser/classes/livechat/items/LiveChatViewerEngagementMessage.ts b/src/parser/classes/livechat/items/LiveChatViewerEngagementMessage.ts
index 003b417c..7947820f 100644
--- a/src/parser/classes/livechat/items/LiveChatViewerEngagementMessage.ts
+++ b/src/parser/classes/livechat/items/LiveChatViewerEngagementMessage.ts
@@ -1,5 +1,5 @@
-import LiveChatTextMessage from './LiveChatTextMessage';
import Parser from '../../../index';
+import LiveChatTextMessage from './LiveChatTextMessage';
class LiveChatViewerEngagementMessage extends LiveChatTextMessage {
static type = 'LiveChatViewerEngagementMessage';
@@ -12,7 +12,7 @@ class LiveChatViewerEngagementMessage extends LiveChatTextMessage {
delete this.author;
delete this.menu_endpoint;
this.icon_type = data.icon.iconType;
- this.action_button = Parser.parse(data.actionButton);
+ this.action_button = Parser.parseItem(data.actionButton);
}
}
diff --git a/src/parser/classes/livechat/items/PollHeader.ts b/src/parser/classes/livechat/items/PollHeader.ts
index bbc61fe0..ce65c2cf 100644
--- a/src/parser/classes/livechat/items/PollHeader.ts
+++ b/src/parser/classes/livechat/items/PollHeader.ts
@@ -18,7 +18,7 @@ class PollHeader extends YTNode {
this.thumbnails = Thumbnail.fromResponse(data.thumbnail);
this.metadata = new Text(data.metadataText);
this.live_chat_poll_type = data.liveChatPollType;
- this.context_menu_button = Parser.parse(data.contextMenuButton);
+ this.context_menu_button = Parser.parseItem(data.contextMenuButton);
}
}
diff --git a/src/parser/index.ts b/src/parser/index.ts
index 3922ef1b..0058e26c 100644
--- a/src/parser/index.ts
+++ b/src/parser/index.ts
@@ -5,11 +5,16 @@ import type PlayerAnnotationsExpanded from './classes/PlayerAnnotationsExpanded'
import type PlayerCaptionsTracklist from './classes/PlayerCaptionsTracklist';
import type PlayerLiveStoryboardSpec from './classes/PlayerLiveStoryboardSpec';
import type PlayerStoryboardSpec from './classes/PlayerStoryboardSpec';
+import type Message from './classes/Message';
+import type LiveChatParticipantsList from './classes/LiveChatParticipantsList';
+import type LiveChatHeader from './classes/LiveChatHeader';
+import type LiveChatItemList from './classes/LiveChatItemList';
import MusicMultiSelectMenuItem from './classes/menus/MusicMultiSelectMenuItem';
import Format from './classes/misc/Format';
import VideoDetails from './classes/misc/VideoDetails';
import NavigationEndpoint from './classes/NavigationEndpoint';
+import Thumbnail from './classes/misc/Thumbnail';
import { InnertubeError, ParsingError } from '../utils/Utils';
import { Memo, observe, ObservedArray, SuperParsedResult, YTNode, YTNodeConstructor } from './helpers';
@@ -496,11 +501,16 @@ export class LiveChatContinuation extends YTNode {
actions: ObservedArray;
action_panel: YTNode | null;
- item_list: YTNode | null;
- header: YTNode | null;
- participants_list: YTNode | null;
- popout_message: YTNode | null;
- emojis: any[] | null; // TODO: give this an actual type
+ item_list: LiveChatItemList | null;
+ header: LiveChatHeader | null;
+ participants_list: LiveChatParticipantsList | null;
+ popout_message: Message | null;
+ emojis: {
+ emoji_id: string;
+ shortcuts: string[];
+ search_terms: string[];
+ image: Thumbnail[];
+ }[];
continuation: TimedContinuation;
viewer_name: string;
@@ -512,18 +522,18 @@ export class LiveChatContinuation extends YTNode {
}), true) || observe([]);
this.action_panel = Parser.parseItem(data.actionPanel);
- this.item_list = Parser.parseItem(data.itemList);
- this.header = Parser.parseItem(data.header);
- this.participants_list = Parser.parseItem(data.participantsList);
- this.popout_message = Parser.parseItem(data.popoutMessage);
+ this.item_list = Parser.parseItem(data.itemList);
+ this.header = Parser.parseItem(data.header);
+ this.participants_list = Parser.parseItem(data.participantsList);
+ this.popout_message = Parser.parseItem(data.popoutMessage);
this.emojis = data.emojis?.map((emoji: any) => ({
emoji_id: emoji.emojiId,
shortcuts: emoji.shortcuts,
search_terms: emoji.searchTerms,
- image: emoji.image,
+ image: Thumbnail.fromResponse(emoji.image),
is_custom_emoji: emoji.isCustomEmoji
- })) || null;
+ })) || [];
this.continuation = new TimedContinuation(
data.continuations?.[0].timedContinuationData ||
diff --git a/src/parser/map.ts b/src/parser/map.ts
index a0fee6c1..a3e2968d 100644
--- a/src/parser/map.ts
+++ b/src/parser/map.ts
@@ -112,6 +112,7 @@ import { default as LiveChat } from './classes/LiveChat';
import { default as AddBannerToLiveChatCommand } from './classes/livechat/AddBannerToLiveChatCommand';
import { default as AddChatItemAction } from './classes/livechat/AddChatItemAction';
import { default as AddLiveChatTickerItemAction } from './classes/livechat/AddLiveChatTickerItemAction';
+import { default as DimChatItemAction } from './classes/livechat/DimChatItemAction';
import { default as LiveChatAutoModMessage } from './classes/livechat/items/LiveChatAutoModMessage';
import { default as LiveChatBanner } from './classes/livechat/items/LiveChatBanner';
import { default as LiveChatBannerHeader } from './classes/livechat/items/LiveChatBannerHeader';
@@ -137,6 +138,7 @@ import { default as RemoveChatItemByAuthorAction } from './classes/livechat/Remo
import { default as ReplaceChatItemAction } from './classes/livechat/ReplaceChatItemAction';
import { default as ReplayChatItemAction } from './classes/livechat/ReplayChatItemAction';
import { default as ShowLiveChatActionPanelAction } from './classes/livechat/ShowLiveChatActionPanelAction';
+import { default as ShowLiveChatDialogAction } from './classes/livechat/ShowLiveChatDialogAction';
import { default as ShowLiveChatTooltipCommand } from './classes/livechat/ShowLiveChatTooltipCommand';
import { default as UpdateDateTextAction } from './classes/livechat/UpdateDateTextAction';
import { default as UpdateDescriptionAction } from './classes/livechat/UpdateDescriptionAction';
@@ -293,6 +295,7 @@ import { default as TwoColumnBrowseResults } from './classes/TwoColumnBrowseResu
import { default as TwoColumnSearchResults } from './classes/TwoColumnSearchResults';
import { default as TwoColumnWatchNextResults } from './classes/TwoColumnWatchNextResults';
import { default as UniversalWatchCard } from './classes/UniversalWatchCard';
+import { default as UpsellDialog } from './classes/UpsellDialog';
import { default as VerticalList } from './classes/VerticalList';
import { default as VerticalWatchCardList } from './classes/VerticalWatchCardList';
import { default as Video } from './classes/Video';
@@ -419,6 +422,7 @@ export const YTNodes = {
AddBannerToLiveChatCommand,
AddChatItemAction,
AddLiveChatTickerItemAction,
+ DimChatItemAction,
LiveChatAutoModMessage,
LiveChatBanner,
LiveChatBannerHeader,
@@ -444,6 +448,7 @@ export const YTNodes = {
ReplaceChatItemAction,
ReplayChatItemAction,
ShowLiveChatActionPanelAction,
+ ShowLiveChatDialogAction,
ShowLiveChatTooltipCommand,
UpdateDateTextAction,
UpdateDescriptionAction,
@@ -600,6 +605,7 @@ export const YTNodes = {
TwoColumnSearchResults,
TwoColumnWatchNextResults,
UniversalWatchCard,
+ UpsellDialog,
VerticalList,
VerticalWatchCardList,
Video,
diff --git a/src/parser/youtube/LiveChat.ts b/src/parser/youtube/LiveChat.ts
index 30aeb536..99a7e647 100644
--- a/src/parser/youtube/LiveChat.ts
+++ b/src/parser/youtube/LiveChat.ts
@@ -43,11 +43,11 @@ export type ChatAction =
export type ChatItemWithMenu = LiveChatAutoModMessage | LiveChatMembershipItem | LiveChatPaidMessage | LiveChatPaidSticker | LiveChatTextMessage | LiveChatViewerEngagementMessage;
export interface LiveMetadata {
- title: UpdateTitleAction | undefined;
- description: UpdateDescriptionAction | undefined;
- views: UpdateViewershipAction | undefined;
- likes: UpdateToggleButtonTextAction | undefined;
- date: UpdateDateTextAction | undefined;
+ title?: UpdateTitleAction;
+ description?: UpdateDescriptionAction;
+ views?: UpdateViewershipAction;
+ likes?: UpdateToggleButtonTextAction;
+ date?: UpdateDateTextAction;
}
class LiveChat extends EventEmitter {
@@ -69,10 +69,19 @@ class LiveChat extends EventEmitter {
this.#video_id = video_info.basic_info.id as string;
this.#channel_id = video_info.basic_info.channel_id as string;
this.#actions = video_info.actions;
- this.#continuation = video_info.livechat?.continuation || undefined;
+ this.#continuation = video_info.livechat?.continuation;
this.is_replay = video_info.livechat?.is_replay || false;
}
+ on(type: 'start', listener: (initial_data: LiveChatContinuation) => void): void;
+ on(type: 'chat-update', listener: (action: ChatAction) => void): void;
+ on(type: 'metadata-update', listener: (metadata: LiveMetadata) => void): void;
+ on(type: 'error', listener: (err: Error) => void): void;
+ on(type: 'end', listener: () => void): void;
+ on(type: string, listener: (...args: any[]) => void): void {
+ super.on(type, listener);
+ }
+
start() {
if (!this.running) {
this.running = true;
@@ -87,32 +96,44 @@ class LiveChat extends EventEmitter {
#pollLivechat() {
(async () => {
- const endpoint = this.is_replay ? 'live_chat/get_live_chat_replay' : 'live_chat/get_live_chat';
- const response = await this.#actions.execute(endpoint, { continuation: this.#continuation });
+ try {
+ const endpoint = this.is_replay ? 'live_chat/get_live_chat_replay' : 'live_chat/get_live_chat';
+ const response = await this.#actions.execute(endpoint, { continuation: this.#continuation });
- const data = Parser.parseResponse(response.data);
- const contents = data.continuation_contents;
+ const data = Parser.parseResponse(response.data);
+ const contents = data.continuation_contents;
- if (!(contents instanceof LiveChatContinuation))
- throw new InnertubeError('Continuation is not a LiveChatContinuation');
+ if (!(contents instanceof LiveChatContinuation)) {
+ this.stop();
+ this.emit('end');
+ return;
+ }
- this.#continuation = contents.continuation.token;
+ this.#continuation = contents.continuation.token;
- // Header only exists in the first request
- if (contents.header) {
- this.initial_info = contents;
- this.emit('start', contents);
- } else {
- await this.#emitSmoothedActions(contents.actions);
+ // Header only exists in the first request
+ if (contents.header) {
+ this.initial_info = contents;
+ this.emit('start', contents);
+ } else {
+ await this.#emitSmoothedActions(contents.actions);
+ }
+
+ /**
+ * If there are no actions then we wait 1000 milliseconds, otherwise
+ * the amount of items on the action queue will determine the polling interval.
+ */
+ if (!contents.actions.length && !contents.header)
+ await this.#wait(1000);
+
+ if (this.running)
+ this.#pollLivechat();
+ } catch (err) {
+ this.emit('error', new InnertubeError('Failed to poll livechat, retrying...', err));
+ await this.#wait(2000);
+ if (this.running)
+ this.#pollLivechat();
}
-
- // If there are no actions then we wait 1000 milliseconds, otherwise
- // The amount of items on the action queue will determine the polling interval.
- if (!contents.actions.length && !contents.header)
- await this.#wait(1000);
-
- if (this.running)
- this.#pollLivechat();
})();
}
@@ -141,39 +162,47 @@ class LiveChat extends EventEmitter {
#pollMetadata() {
(async () => {
- const payload: {
- videoId: string | undefined;
- continuation?: string;
- } = { videoId: this.#video_id };
+ try {
+ const payload: {
+ videoId?: string;
+ continuation?: string;
+ } = { videoId: this.#video_id };
- if (this.#mcontinuation) {
- payload.continuation = this.#mcontinuation;
+ if (this.#mcontinuation) {
+ payload.continuation = this.#mcontinuation;
+ }
+
+ const response = await this.#actions.execute('/updated_metadata', payload);
+ const data = Parser.parseResponse(response.data);
+
+ this.#mcontinuation = data.continuation?.token;
+
+ this.metadata = {
+ title: data.actions?.array().firstOfType(UpdateTitleAction) || this.metadata?.title,
+ description: data.actions?.array().firstOfType(UpdateDescriptionAction) || this.metadata?.description,
+ views: data.actions?.array().firstOfType(UpdateViewershipAction) || this.metadata?.views,
+ likes: data.actions?.array().firstOfType(UpdateToggleButtonTextAction) || this.metadata?.likes,
+ date: data.actions?.array().firstOfType(UpdateDateTextAction) || this.metadata?.date
+ };
+
+ this.emit('metadata-update', this.metadata);
+
+ await this.#wait(5000);
+
+ if (this.running)
+ this.#pollMetadata();
+ } catch (err) {
+ this.emit('error', new InnertubeError('Failed to poll live metadata, retrying...', err));
+ await this.#wait(2000);
+ if (this.running)
+ this.#pollMetadata();
}
-
- const response = await this.#actions.execute('/updated_metadata', payload);
- const data = Parser.parseResponse(response.data);
-
- this.#mcontinuation = data.continuation?.token;
-
- this.metadata = {
- title: data.actions?.array().firstOfType(UpdateTitleAction) || this.metadata?.title,
- description: data.actions?.array().firstOfType(UpdateDescriptionAction) || this.metadata?.description,
- views: data.actions?.array().firstOfType(UpdateViewershipAction) || this.metadata?.views,
- likes: data.actions?.array().firstOfType(UpdateToggleButtonTextAction) || this.metadata?.likes,
- date: data.actions?.array().firstOfType(UpdateDateTextAction) || this.metadata?.date
- };
-
- this.emit('metadata-update', this.metadata);
-
- await this.#wait(5000);
-
- if (this.running)
- this.#pollMetadata();
})();
}
/**
* Sends a message.
+ * @param text - Text to send.
*/
async sendMessage(text: string): Promise> {
const response = await this.#actions.execute('/live_chat/send_message', {
@@ -189,6 +218,25 @@ class LiveChat extends EventEmitter {
return response.actions.array().as(AddChatItemAction);
}
+ /**
+ * Applies given filter to the live chat.
+ * @param filter - Filter to apply.
+ */
+ applyFilter(filter: 'TOP_CHAT' | 'LIVE_CHAT'): void {
+ if (!this.initial_info)
+ throw new InnertubeError('Cannot apply filter before initial info is retrieved.');
+
+ const menu_items = this.initial_info?.header?.view_selector?.sub_menu_items;
+
+ if (filter === 'TOP_CHAT') {
+ if (menu_items?.at(0)?.selected) return;
+ this.#continuation = menu_items?.at(0)?.continuation;
+ } else {
+ if (menu_items?.at(1)?.selected) return;
+ this.#continuation = menu_items?.at(1)?.continuation;
+ }
+ }
+
/**
* Retrieves given chat item's menu.
*/
diff --git a/test/main.test.ts b/test/main.test.ts
index 5c357e71..07c6f044 100644
--- a/test/main.test.ts
+++ b/test/main.test.ts
@@ -77,14 +77,6 @@ describe('YouTube.js Tests', () => {
expect(search.channels).toBeDefined();
expect(search.has_continuation).toBe(true);
});
-
- it('should search with WatchCardHeroVideo parse', async () => {
- search = await yt.search(VIDEOS[2].QUERY);
- expect(search.results.length).toBeGreaterThanOrEqual(5);
- expect(search.playlists).toBeDefined();
- expect(search.channels).toBeDefined();
- expect(search.has_continuation).toBe(true);
- });
it('should retrieve search continuation', async () => {
const next = await search.getContinuation();