mirror of
https://github.com/LuanRT/YouTube.js.git
synced 2026-06-19 12:31:17 +00:00
refactor: improve livechat parser & add remaining action nodes (#285)
* refactor: improve live chat parsers & add missing nodes * chore: update example and docs * docs: rephrasing/formatting * chore: remove unneeded test (unrelated)
This commit is contained in:
@@ -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.
|
||||
|
||||
<a name="start"></a>
|
||||
### start()
|
||||
@@ -59,6 +79,15 @@ Starts the Live Chat.
|
||||
### stop()
|
||||
Stops the Live Chat.
|
||||
|
||||
<a name="applyfilter"></a>
|
||||
### applyFilter(filter)
|
||||
|
||||
Applies given filter to the live chat.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| filter | `string` | Can be `TOP_CHAT` or `LIVE_CHAT` |
|
||||
|
||||
<a name="getitemmenu"></a>
|
||||
### getItemMenu(item)
|
||||
Retrieves given chat item's menu.
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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<Menu>(data.overflowMenu);
|
||||
this.collapse_button = Parser.parseItem<Button>(data.collapseButton);
|
||||
this.view_selector = Parser.parseItem<SortFilterSubMenu>(data.viewSelector);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Button>(data.moreCommentsBelowButton);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Button>(data.sendButton);
|
||||
this.target_id = data.targetId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LiveChatParticipant>;
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.title = new Text(data.title);
|
||||
this.participants = Parser.parse(data.participants);
|
||||
this.participants = Parser.parseArray<LiveChatParticipant>(data.participants);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}));
|
||||
|
||||
25
src/parser/classes/UpsellDialog.ts
Normal file
25
src/parser/classes/UpsellDialog.ts
Normal file
@@ -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<Button>(data.actionButton);
|
||||
this.dismiss_button = Parser.parseItem<Button>(data.dismissButton);
|
||||
this.is_visible = data.isVisible;
|
||||
}
|
||||
}
|
||||
|
||||
export default UpsellDialog;
|
||||
@@ -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<LiveChatBanner>(data.bannerRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
14
src/parser/classes/livechat/DimChatItemAction.ts
Normal file
14
src/parser/classes/livechat/DimChatItemAction.ts
Normal file
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LiveChatActionPanel>(data.panelToShow, LiveChatActionPanel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
15
src/parser/classes/livechat/ShowLiveChatDialogAction.ts
Normal file
15
src/parser/classes/livechat/ShowLiveChatDialogAction.ts
Normal file
@@ -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;
|
||||
@@ -8,7 +8,7 @@ class ShowLiveChatTooltipCommand extends YTNode {
|
||||
|
||||
constructor(data: any) {
|
||||
super();
|
||||
this.tooltip = Parser.parse(data.tooltip);
|
||||
this.tooltip = Parser.parseItem(data.tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Button>(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;
|
||||
|
||||
@@ -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<LiveChatBannerHeader>(data.header);
|
||||
this.contents = Parser.parseItem(data.contents);
|
||||
this.action_id = data.actionId;
|
||||
this.viewer_is_creator = data.viewerIsCreator;
|
||||
this.target_id = data.targetId;
|
||||
|
||||
@@ -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<Button>(data.contextMenuButton);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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<LiveChatAuthorBadge | MetadataBadge>(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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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<LiveChatAuthorBadge | MetadataBadge>(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<LiveChatAuthorBadge | MetadataBadge>(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;
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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<LiveChatAuthorBadge | MetadataBadge>(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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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<LiveChatAuthorBadge | MetadataBadge>(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<LiveChatAuthorBadge | MetadataBadge>(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;
|
||||
}
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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<LiveChatAuthorBadge | MetadataBadge>(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<LiveChatAuthorBadge | MetadataBadge>(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;
|
||||
}
|
||||
|
||||
@@ -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<LiveChatAuthorBadge | MetadataBadge>;
|
||||
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<LiveChatAuthorBadge | MetadataBadge>(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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<YTNode>;
|
||||
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<YTNode>([]);
|
||||
|
||||
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<LiveChatItemList>(data.itemList);
|
||||
this.header = Parser.parseItem<LiveChatHeader>(data.header);
|
||||
this.participants_list = Parser.parseItem<LiveChatParticipantsList>(data.participantsList);
|
||||
this.popout_message = Parser.parseItem<Message>(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 ||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<ObservedArray<AddChatItemAction>> {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user