diff --git a/CHANGELOG.md b/CHANGELOG.md index e951f6b..afabd21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,9 @@ - `mc.getTranscript()` - Playlist API - `mc.getPlaylist()` -- New: `ModerationMessageAction` (type: `moderationMessageAction`) +- New: `ModerationMessageAction` (type: `moderationMessageAction`) for moderation messages for moderators +- New: `AddRedirectBannerAction` (type: `addRedirectBannerAction`) for raid event notifications +- Add support for `Raid` event ### Improvements diff --git a/src/chat/actions/addBannerToLiveChatCommand.ts b/src/chat/actions/addBannerToLiveChatCommand.ts index 6acab85..b72a6cf 100644 --- a/src/chat/actions/addBannerToLiveChatCommand.ts +++ b/src/chat/actions/addBannerToLiveChatCommand.ts @@ -1,4 +1,7 @@ -import { AddBannerAction } from "../../interfaces/actions"; +import { + AddBannerAction, + AddRedirectBannerAction, +} from "../../interfaces/actions"; import { YTAddBannerToLiveChatCommand } from "../../interfaces/yt/chat"; import { debugLog, stringify, tsToDate } from "../../utils"; import { parseBadges } from "../badge"; @@ -10,16 +13,10 @@ export function parseAddBannerToLiveChatCommand( // add pinned item const bannerRdr = payload["bannerRenderer"]["liveChatBannerRenderer"]; - if (!bannerRdr.header) { - throw new Error( - "[action required] Invalid banner header: " + JSON.stringify(bannerRdr) - ); - } - - if (bannerRdr.header.liveChatBannerHeaderRenderer.icon.iconType !== "KEEP") { + if (bannerRdr.header?.liveChatBannerHeaderRenderer.icon.iconType !== "KEEP") { debugLog( "[action required] Unknown icon type (addBannerToLiveChatCommand)", - JSON.stringify(bannerRdr.header.liveChatBannerHeaderRenderer.icon) + JSON.stringify(bannerRdr.header) ); } @@ -28,48 +25,70 @@ export function parseAddBannerToLiveChatCommand( const targetId = bannerRdr.targetId; const viewerIsCreator = bannerRdr.viewerIsCreator; - // header - const header = bannerRdr.header.liveChatBannerHeaderRenderer; - const title = header.text.runs; - // contents - const liveChatRdr = bannerRdr.contents.liveChatTextMessageRenderer; - const id = liveChatRdr.id; - const message = liveChatRdr.message.runs; - const timestampUsec = liveChatRdr.timestampUsec; - const timestamp = tsToDate(timestampUsec); - const authorName = stringify(liveChatRdr.authorName); - const authorPhoto = pickThumbUrl(liveChatRdr.authorPhoto); - const authorChannelId = liveChatRdr.authorExternalChannelId; - const { isVerified, isOwner, isModerator, membership } = - parseBadges(liveChatRdr); + const contents = bannerRdr.contents; - if (!authorName) { - debugLog( - "[action required] Empty authorName found at addBannerToLiveChatCommand", - JSON.stringify(liveChatRdr) + if ("liveChatTextMessageRenderer" in contents) { + const rdr = contents.liveChatTextMessageRenderer; + const id = rdr.id; + const message = rdr.message.runs; + const timestampUsec = rdr.timestampUsec; + const timestamp = tsToDate(timestampUsec); + const authorName = stringify(rdr.authorName); + const authorPhoto = pickThumbUrl(rdr.authorPhoto); + const authorChannelId = rdr.authorExternalChannelId; + const { isVerified, isOwner, isModerator, membership } = parseBadges(rdr); + + // header + const header = bannerRdr.header!.liveChatBannerHeaderRenderer; + const title = header.text.runs; + + if (!authorName) { + debugLog( + "[action required] Empty authorName found at addBannerToLiveChatCommand", + JSON.stringify(rdr) + ); + } + + const parsed: AddBannerAction = { + type: "addBannerAction", + actionId, + targetId, + id, + title, + message, + timestampUsec, + timestamp, + authorName, + authorPhoto, + authorChannelId, + isVerified, + isOwner, + isModerator, + membership, + viewerIsCreator, + contextMenuEndpointParams: + rdr.contextMenuEndpoint?.liveChatItemContextMenuEndpoint.params, + }; + return parsed; + } else if ("liveChatBannerRedirectRenderer" in contents) { + // TODO: + const rdr = contents.liveChatBannerRedirectRenderer; + const authorName = rdr.bannerMessage.runs[0].text; + const authorPhoto = pickThumbUrl(rdr.authorPhoto); + const payload: AddRedirectBannerAction = { + type: "addRedirectBannerAction", + actionId, + targetId, + authorName, + authorPhoto, + }; + return payload; + } else { + throw new Error( + `[action required] Unrecognized content type found in parseAddBannerToLiveChatCommand: ${JSON.stringify( + payload + )}` ); } - - const parsed: AddBannerAction = { - type: "addBannerAction", - actionId, - targetId, - id, - title, - message, - timestampUsec, - timestamp, - authorName, - authorPhoto, - authorChannelId, - isVerified, - isOwner, - isModerator, - membership, - viewerIsCreator, - contextMenuEndpointParams: - liveChatRdr.contextMenuEndpoint?.liveChatItemContextMenuEndpoint.params, - }; - return parsed; } diff --git a/src/interfaces/actions.ts b/src/interfaces/actions.ts index 3b7d3a2..dc70f10 100644 --- a/src/interfaces/actions.ts +++ b/src/interfaces/actions.ts @@ -31,6 +31,7 @@ export type Action = | AddMembershipTickerAction | AddBannerAction | RemoveBannerAction + | AddRedirectBannerAction | AddViewerEngagementMessageAction | ShowPanelAction | ShowPollPanelAction @@ -265,6 +266,14 @@ export interface RemoveBannerAction { targetActionId: string; } +export interface AddRedirectBannerAction { + type: "addRedirectBannerAction"; + actionId: string; + targetId: string; + authorName: string; + authorPhoto: string; +} + export interface ShowTooltipAction { type: "showTooltipAction"; targetId: string; diff --git a/src/interfaces/yt/chat.ts b/src/interfaces/yt/chat.ts index f39bb29..16007d4 100644 --- a/src/interfaces/yt/chat.ts +++ b/src/interfaces/yt/chat.ts @@ -371,6 +371,10 @@ export interface YTLiveChatModerationMessageRendererContainer { liveChatModerationMessageRenderer: YTLiveChatModerationMessageRenderer; } +export interface YTLiveChatBannerRedirectRendererContainer { + liveChatBannerRedirectRenderer: YTLiveChatBannerRedirectRenderer; +} + // LiveChat Renderers export interface YTLiveChatTextMessageRenderer { @@ -461,7 +465,9 @@ export interface YTLiveChatPlaceholderItemRenderer { export interface YTLiveChatBannerRenderer { actionId: string; targetId: string; // live-chat-banner - contents: YTLiveChatTextMessageRendererContainer; + contents: + | YTLiveChatTextMessageRendererContainer + | YTLiveChatBannerRedirectRendererContainer; header?: YTLiveChatBannerRendererHeader; viewerIsCreator: boolean; } @@ -510,6 +516,28 @@ export interface YTLiveChatActionPanelRenderer { targetId: string; } +export interface YTLiveChatBannerRedirectRenderer { + /** + * "runs": [ + { + "text": "Athena Nightingale【AkioAIR】", + "bold": true, + "textColor": 4294967295, + "fontFace": "FONT_FACE_ROBOTO_REGULAR" + }, + { + "text": " and their viewers just joined. Say hello!", + "textColor": 4294967295, + "fontFace": "FONT_FACE_ROBOTO_REGULAR" + } + ] + */ + bannerMessage: YTRunContainer; + authorPhoto: YTThumbnailList; + inlineActionButton: YTActionButtonRendererContainer; + contextMenuButton: YTContextMenuButtonRendererContainer; +} + export interface YTLiveChatPollChoice { text: YTText; selected: boolean;