From 8e37efa575ca79f199875f29b81e2b9370dc4e87 Mon Sep 17 00:00:00 2001 From: LuanRT Date: Tue, 10 Jan 2023 01:44:51 -0300 Subject: [PATCH] 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) --- examples/livechat/README.md | 41 ++++- examples/livechat/index.ts | 47 +++++- src/parser/classes/LiveChatHeader.ts | 15 +- src/parser/classes/LiveChatItemList.ts | 5 +- src/parser/classes/LiveChatMessageInput.ts | 5 +- .../classes/LiveChatParticipantsList.ts | 7 +- src/parser/classes/SortFilterSubMenu.ts | 4 +- src/parser/classes/UpsellDialog.ts | 25 +++ .../livechat/AddBannerToLiveChatCommand.ts | 5 +- .../livechat/AddLiveChatTickerItemAction.ts | 2 +- .../classes/livechat/DimChatItemAction.ts | 14 ++ .../classes/livechat/ReplaceChatItemAction.ts | 2 +- .../classes/livechat/ReplayChatItemAction.ts | 4 +- .../livechat/ShowLiveChatActionPanelAction.ts | 5 +- .../livechat/ShowLiveChatDialogAction.ts | 15 ++ .../livechat/ShowLiveChatTooltipCommand.ts | 2 +- .../livechat/UpdateLiveChatPollAction.ts | 2 +- .../livechat/items/LiveChatAutoModMessage.ts | 9 +- .../classes/livechat/items/LiveChatBanner.ts | 10 +- .../livechat/items/LiveChatBannerHeader.ts | 11 +- .../livechat/items/LiveChatBannerPoll.ts | 4 +- .../livechat/items/LiveChatMembershipItem.ts | 21 ++- .../livechat/items/LiveChatPaidMessage.ts | 18 +- .../livechat/items/LiveChatPaidSticker.ts | 25 ++- .../livechat/items/LiveChatProductItem.ts | 4 +- .../livechat/items/LiveChatTextMessage.ts | 15 +- .../items/LiveChatTickerPaidMessageItem.ts | 23 +-- .../items/LiveChatTickerPaidStickerItem.ts | 23 +-- .../items/LiveChatTickerSponsorItem.ts | 26 ++- .../items/LiveChatViewerEngagementMessage.ts | 4 +- .../classes/livechat/items/PollHeader.ts | 2 +- src/parser/index.ts | 32 ++-- src/parser/map.ts | 6 + src/parser/youtube/LiveChat.ts | 154 ++++++++++++------ test/main.test.ts | 8 - 35 files changed, 413 insertions(+), 182 deletions(-) create mode 100644 src/parser/classes/UpsellDialog.ts create mode 100644 src/parser/classes/livechat/DimChatItemAction.ts create mode 100644 src/parser/classes/livechat/ShowLiveChatDialogAction.ts 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