Skip to content

Commit

Permalink
Implement rudimentary redirect banner
Browse files Browse the repository at this point in the history
Supports send and receive, uses the urlEndpoint or watchEndpoint link to open a new tab with the given url if the button is clicked
This also adds support for bold text (but not deemphasized text yet) to MessageRun
This does not implement contextMenuButton at all yet, only inlineActionButton.
  • Loading branch information
FlaminSarge committed Dec 25, 2024
1 parent cff36d3 commit eb1fa02
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 14 deletions.
13 changes: 12 additions & 1 deletion src/components/Hyperchat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
ytDark
} from '../ts/storage';
import { version } from '../manifest.json';
import RedirectBanner from './RedirectBanner.svelte';
const welcome = { welcome: true, message: { messageId: 'welcome' } };
type Welcome = typeof welcome;
Expand All @@ -56,6 +57,8 @@
const messageKeys = new Set<string>();
let pinned: Ytc.ParsedPinned | null;
let summary: Ytc.ParsedSummary | null;
let redirect: Ytc.ParsedRedirect | null;
$: hasBanner = pinned || redirect || (summary && $showChatSummary);
let div: HTMLElement;
let isAtBottom = true;
let truncateInterval: number;
Expand Down Expand Up @@ -189,6 +192,9 @@
case 'summary':
summary = action;
break;
case 'redirect':
redirect = action;
break;
case 'pin':
pinned = action;
break;
Expand Down Expand Up @@ -399,13 +405,18 @@
</div>
{/each}
</div>
{#if (summary && $showChatSummary) || pinned}
{#if hasBanner}
<div class="absolute top-0 w-full" bind:this={topBar}>
{#if summary && $showChatSummary}
<div class="mx-1.5 mt-1.5">
<ChatSummary summary={summary} on:resize={topBarResized} />
</div>
{/if}
{#if redirect}
<div class="mx-1.5 mt-1.5">
<RedirectBanner redirect={redirect} on:resize={topBarResized} />
</div>
{/if}
{#if pinned}
<div class="mx-1.5 mt-1.5">
<PinnedMessage pinned={pinned} on:resize={topBarResized} />
Expand Down
8 changes: 7 additions & 1 deletion src/components/MessageRuns.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,13 @@
{#if deleted}
<span>{run.text}</span>
{:else}
<TranslatedMessage text={run.text} {forceTLColor} />
{#if run.styles?.includes('bold')}
<strong>
<TranslatedMessage text={run.text} {forceTLColor} />
</strong>
{:else}
<TranslatedMessage text={run.text} {forceTLColor} />
{/if}
{/if}
{:else if run.type === 'link'}
<a
Expand Down
88 changes: 88 additions & 0 deletions src/components/RedirectBanner.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<script lang="ts">
import { slide, fade } from 'svelte/transition';
import MessageRun from './MessageRuns.svelte';
import Tooltip from './common/Tooltip.svelte';
import Icon from 'smelte/src/components/Icon';
import { Theme } from '../ts/chat-constants';
import { createEventDispatcher } from 'svelte';
import { showProfileIcons } from '../ts/storage';
import Button from 'smelte/src/components/Button';
export let redirect: Ytc.ParsedRedirect;
let dismissed = false;
let shorten = false;
let autoHideTimeout: NodeJS.Timeout | null = null;
const classes = 'rounded inline-flex flex-col overflow-visible ' +
'bg-secondary-900 p-2 w-full text-white z-10 shadow';
const onShorten = () => {
shorten = !shorten;
if (autoHideTimeout) {
clearTimeout(autoHideTimeout);
autoHideTimeout = null;
}
};
$: if (redirect) {
dismissed = false;
shorten = false;
if (redirect.showtime) {
autoHideTimeout = setTimeout(() => { shorten = true; }, redirect.showtime);
}
}
const dispatch = createEventDispatcher();
$: dismissed, shorten, dispatch('resize');
</script>

{#if !dismissed}
<div
class={classes}
transition:fade={{ duration: 250 }}
>
<div class="flex flex-row items-center cursor-pointer" on:click={onShorten}>
<div class="font-medium tracking-wide text-white flex-1">
<span class="mr-1 inline-block" style="transform: translateY(3px);">
<Icon small>
{#if shorten}
expand_more
{:else}
expand_less
{/if}
</Icon>
</span>
<span class="align-middle">Redirect Notice</span>
</div>
<div class="flex-none self-end" style="transform: translateY(3px);">
<Tooltip offsetY={0} small>
<Icon
slot="activator"
class="cursor-pointer text-lg"
on:click={() => { dismissed = true; }}
>
close
</Icon>
Dismiss
</Tooltip>
</div>
</div>
{#if !shorten && !dismissed}
<div class="mt-1 whitespace-pre-line" transition:slide|local={{ duration: 300 }}>
{#if $showProfileIcons}
<img
class="h-5 w-5 inline align-middle rounded-full flex-none"
src={redirect.item.profileIcon.src}
alt={redirect.item.profileIcon.alt}
/>
{/if}
<MessageRun runs={redirect.item.message} forceDark forceTLColor={Theme.DARK}/>
</div>
<div class="mt-1 whitespace-pre-line" transition:slide|local={{ duration: 300 }}>
<Button href={redirect.item.action.url} target="_blank" small>
<MessageRun runs={redirect.item.action.text} forceDark forceTLColor={Theme.DARK}/>
</Button>
</div>
{/if}
</div>
{/if}
34 changes: 33 additions & 1 deletion src/ts/chat-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const parseMessageRuns = (runs?: Ytc.MessageRun[]): Ytc.ParsedRun[] => {
} else if (run.text != null) {
parsedRuns.push({
type: 'text',
styles: (run.bold ? ['bold'] : []).concat(run.deemphasize ? ['deemphasize'] : []),
text: decodeURIComponent(escape(unescape(encodeURIComponent(
run.text
))))
Expand Down Expand Up @@ -106,6 +107,34 @@ const parseChatSummary = (renderer: Ytc.AddChatItem, showtime: number): Ytc.Pars
return item;
}

const parseRedirectBanner = (renderer: Ytc.AddChatItem, showtime: number): Ytc.ParsedRedirect | undefined => {
if (!renderer.liveChatBannerRedirectRenderer) {
return;
}
const baseRenderer = renderer.liveChatBannerRedirectRenderer!;
const profileIcon = {
src: fixUrl(baseRenderer.authorPhoto?.thumbnails[0].url ?? ''),
alt: 'Redirect profile icon'
};
const url = baseRenderer.inlineActionButton?.buttonRenderer.command.urlEndpoint?.url ||
(baseRenderer.inlineActionButton?.buttonRenderer.command.watchEndpoint?.videoId ?
"/watch?v=" + baseRenderer.inlineActionButton?.buttonRenderer.command.watchEndpoint?.videoId
: '');
const item: Ytc.ParsedRedirect = {
type: 'redirect',
item: {
message: parseMessageRuns(baseRenderer.bannerMessage.runs),
profileIcon: profileIcon,
action: {
url: fixUrl(url),
text: parseMessageRuns(baseRenderer.inlineActionButton?.buttonRenderer.text?.runs),
}
},
showtime: showtime,
};
return item;
}

const parseAddChatItemAction = (action: Ytc.AddChatItemAction, isReplay = false, liveTimeoutOrReplayMs = 0): Ytc.ParsedMessage | undefined => {
const actionItem = action.item;
const renderer = actionItem.liveChatTextMessageRenderer ??
Expand Down Expand Up @@ -228,7 +257,7 @@ const parseMessageDeletedAction = (action: Ytc.MessageDeletedAction): Ytc.Parsed
};
};

const parseBannerAction = (action: Ytc.AddPinnedAction): Ytc.ParsedPinned | Ytc.ParsedSummary | undefined => {
const parseBannerAction = (action: Ytc.AddPinnedAction): Ytc.ParsedMisc | undefined => {
const baseRenderer = action.bannerRenderer.liveChatBannerRenderer;

// fold both auto-disappear and auto-collapse into just collapse for showtime
Expand All @@ -239,6 +268,9 @@ const parseBannerAction = (action: Ytc.AddPinnedAction): Ytc.ParsedPinned | Ytc.
if (baseRenderer.contents.liveChatBannerChatSummaryRenderer) {
return parseChatSummary(baseRenderer.contents, showtime);
}
if (baseRenderer.contents.liveChatBannerRedirectRenderer) {
return parseRedirectBanner(baseRenderer.contents, showtime);
}
const parsedContents = parseAddChatItemAction(
{ item: baseRenderer.contents }, true
);
Expand Down
67 changes: 56 additions & 11 deletions src/ts/typings/ytc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,16 +145,14 @@ declare namespace Ytc {
}

interface ThumbnailsWithLabel extends Thumbnails {
accessibility?: {
accessibilityData: {
label: string;
};
};
accessibility?: AccessibilityObj;
}

/** Message run object */
interface MessageRun {
text?: string;
bold?: boolean;
deemphasize?: boolean;
navigationEndpoint?: {
commandMetadata: {
webCommandMetadata: {
Expand Down Expand Up @@ -185,11 +183,7 @@ declare namespace Ytc {
/** Unlocalized string */
iconType: string;
};
accessibility?: {
accessibilityData: {
label: string;
};
};
accessibility?: AccessibilityObj;
};
}

Expand Down Expand Up @@ -248,6 +242,35 @@ declare namespace Ytc {
};
}

interface RedirectRenderer {
bannerMessage: RunsObj;
authorPhoto?: Thumbnails;
inlineActionButton?: {
buttonRenderer: ButtonRenderer;
}
contextMenuButton?: {
buttonRenderer: ButtonRenderer;
}
}

interface ButtonRenderer {
style?: string;
size?: string;
icon?: string;
accessibility?: AccessibilityObj;
isDisabled?: boolean;
text?: RunsObj;
command: {
urlEndpoint?: {
url: string;
target: string;
}
watchEndpoint?: {
videoId: string;
}
}
}

interface PlaceholderRenderer { // No idea what the purpose of this is
id: string;
timestampUsec: IntString;
Expand All @@ -271,6 +294,8 @@ declare namespace Ytc {
liveChatSponsorshipsGiftRedemptionAnnouncementRenderer?: TextMessageRenderer;
/** AI Chat Summary */
liveChatBannerChatSummaryRenderer?: ChatSummaryRenderer;
/** Redirects */
liveChatBannerRedirectRenderer?: RedirectRenderer;
/** ??? */
liveChatPlaceholderItemRenderer?: PlaceholderRenderer;
}
Expand Down Expand Up @@ -299,6 +324,12 @@ declare namespace Ytc {
runs: MessageRun[];
}

interface AccessibilityObj {
accessibilityData: {
label: string;
}
}

/*
* Parsed objects
*/
Expand All @@ -310,6 +341,7 @@ declare namespace Ytc {
interface ParsedTextRun {
type: 'text';
text: string;
styles?: string[];
}

interface ParsedLinkRun {
Expand Down Expand Up @@ -400,13 +432,26 @@ declare namespace Ytc {
showtime: number;
}

interface ParsedRedirect {
type: 'redirect';
item: {
message: ParsedRun[];
profileIcon: ParsedImage;
action: {
url: string;
text: ParsedRun[];
}
};
showtime: number;
}

interface ParsedTicker extends ParsedMessage {
type: 'ticker';
tickerDuration: number;
detailText?: string;
}

type ParsedMisc = ParsedPinned | ParsedSummary | { type: 'unpin' };
type ParsedMisc = ParsedPinned | ParsedSummary | ParsedRedirect | { type: 'unpin' };

type ParsedTimedItem = ParsedMessage | ParsedTicker;

Expand Down

0 comments on commit eb1fa02

Please sign in to comment.