From 1b7e7d38afc6f976d044bbe17178737fde2d6203 Mon Sep 17 00:00:00 2001 From: FlaminSarge Date: Tue, 12 May 2026 17:57:15 -0700 Subject: [PATCH] Restore #168 Was removed by 508dd0f4522933b12dae4b8b7447ab21aea91e2d --- src/components/Hyperchat.svelte | 31 +++++++++-- src/components/PollResults.svelte | 90 +++++++++++++++++++++++++++++++ src/ts/typings/ytc.d.ts | 46 +++++++++++++--- 3 files changed, 156 insertions(+), 11 deletions(-) create mode 100644 src/components/PollResults.svelte diff --git a/src/components/Hyperchat.svelte b/src/components/Hyperchat.svelte index 358ad85e..b3b233c6 100644 --- a/src/components/Hyperchat.svelte +++ b/src/components/Hyperchat.svelte @@ -8,6 +8,7 @@ import PinnedMessage from './PinnedMessage.svelte'; import ChatSummary from './ChatSummary.svelte'; import RedirectBanner from './RedirectBanner.svelte'; + import PollResults from './PollResults.svelte'; import PaidMessage from './PaidMessage.svelte'; import MembershipItem from './MembershipItem.svelte'; import ReportBanDialog from './ReportBanDialog.svelte'; @@ -64,6 +65,7 @@ const TRUNCATE_SIZE = 20; let messageActions: Array = []; const messageKeys = new Set(); + let poll: Ytc.ParsedPoll | null; let pinned: Ytc.ParsedPinned | null; let summary: Ytc.ParsedSummary | null; let redirect: Ytc.ParsedRedirect | null; @@ -92,7 +94,7 @@ // }, // showtime: 5000, // }; - $: hasBanner = Boolean(pinned ?? redirect ?? (summary != null && $showChatSummary)); + $: hasBanner = Boolean(poll ?? pinned ?? redirect ?? (summary != null && $showChatSummary)); let div: HTMLElement; let isAtBottom = true; let truncateInterval: number | undefined; @@ -223,6 +225,9 @@ case 'delete': onDelete(action.deletion); break; + case 'poll': + poll = action; + break; case 'summary': summary = action; break; @@ -233,7 +238,22 @@ pinned = action; break; case 'unpin': - pinned = null; + if (action.targetActionId) { + if (action.targetActionId === pinned?.actionId) { + pinned = null; + } + if (action.targetActionId === summary?.actionId) { + summary = null; + } + if (action.targetActionId === poll?.actionId) { + poll = null; + } + if (action.targetActionId === redirect?.actionId) { + redirect = null; + } + } else { + pinned = null; + } break; case 'playerProgress': $currentProgress = action.playerProgress; @@ -409,7 +429,7 @@ } }, 350); }; - $: $enableStickySuperchatBar, pinned, topBarResized(); + $: $enableStickySuperchatBar, hasBanner, topBarResized(); const isMention = (msg: Ytc.ParsedMessage) => { return $selfChannelName && msg.message.map(run => { @@ -465,6 +485,11 @@ {#if hasBanner}
+ {#if poll} +
+ +
+ {/if} {#if summary && $showChatSummary}
diff --git a/src/components/PollResults.svelte b/src/components/PollResults.svelte new file mode 100644 index 00000000..a25a3c6e --- /dev/null +++ b/src/components/PollResults.svelte @@ -0,0 +1,90 @@ + + +{#if !dismissed} +
+
+
+ + + {#if shorten} + expand_more + {:else} + expand_less + {/if} + + + {#if $showProfileIcons} + {poll.item.profileIcon.alt} + {/if} + {#each poll.item.header as run} + {#if run.type === 'text'} + {run.text} + {/if} + {/each} +
+
+ + { dismissed = true; }} + > + close + + Dismiss + +
+
+ {#if !shorten && !dismissed} +
+ +
+ {#each poll.item.choices as choice} +
+ + + {choice.percentage} + +
+ + {/each} + {/if} +
+{/if} diff --git a/src/ts/typings/ytc.d.ts b/src/ts/typings/ytc.d.ts index c513efde..be0de96b 100644 --- a/src/ts/typings/ytc.d.ts +++ b/src/ts/typings/ytc.d.ts @@ -268,8 +268,17 @@ declare namespace Ytc { icon?: string; accessibility?: AccessibilityObj; isDisabled?: boolean; - text?: RunsObj; + text?: RunsObj; // | SimpleTextObj; command: { + commandMetadata?: { + webCommandMetadata?: { + apiUrl?: string; + sendPost?: boolean; + } + } + liveChatActionEndpoint?: { + params: string; + } urlEndpoint?: { url: string; target: string; @@ -280,6 +289,19 @@ declare namespace Ytc { } } + interface EngagementMessageRenderer { + message: RunsObj[]; + id: string; + timestampUsec?: IntString; + icon?: { + /** Unlocalized string */ + iconType: string; + }; + actionButton?: { + buttonRenderer: ButtonRenderer; + } + } + interface PlaceholderRenderer { // No idea what the purpose of this is id: string; timestampUsec: IntString; @@ -292,14 +314,19 @@ declare namespace Ytc { pollQuestion: RunsObj; metadataText: RunsObj; thumbnail?: Thumbnails; + liveChatPollType?: string; }; }; - choices: Array<{ - text: RunsObj; - selected: boolean; - voteRatio?: number; - votePercentage?: SimpleTextObj; - }>; + choices: PollChoice[]; + displayVoteResults?: boolean; + button?: ButtonRenderer; + } + + interface PollChoice { + text: RunsObj; + selected: boolean; + voteRatio?: number; + votePercentage?: SimpleTextObj; } type Renderers = TextMessageRenderer | PaidMessageRenderer | @@ -324,8 +351,10 @@ declare namespace Ytc { liveChatBannerRedirectRenderer?: RedirectRenderer; /** ??? */ liveChatPlaceholderItemRenderer?: PlaceholderRenderer; - /** Poll */ + /** Poll start */ pollRenderer?: PollRenderer; + /** Poll end + other in-chat announcements TODO */ + liveChatViewerEngagementMessageRenderer?: EngagementMessageRenderer; } interface TickerRenderer { // Doesn't have a timestamp but ID is always a paid message id @@ -491,6 +520,7 @@ declare namespace Ytc { percentage?: string; }>; }; + // TODO add 'action' for ending poll button } interface ParsedRemoveBanner {