From 580072b8ecce978b2e57e8a281f9e46e00d5521a Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Wed, 13 Jul 2022 23:34:34 -0700 Subject: [PATCH 01/16] man this is way too hard lmao aint no way --- src/components/Message.svelte | 14 ++- src/scripts/chat-interceptor.ts | 194 +++++++++++++++++--------------- src/ts/chat-actions.ts | 8 ++ src/ts/storage.ts | 1 + src/ts/typings/chat.d.ts | 17 ++- 5 files changed, 138 insertions(+), 96 deletions(-) diff --git a/src/components/Message.svelte b/src/components/Message.svelte index e91fa515..48821d99 100644 --- a/src/components/Message.svelte +++ b/src/components/Message.svelte @@ -9,10 +9,11 @@ showUserBadges, hoveredItem, port, - selfChannelId + selfChannelId, + hasMembershipGiftingEnabled } from '../ts/storage'; import { chatUserActionsItems, Theme } from '../ts/chat-constants'; - import { useBanHammer } from '../ts/chat-actions'; + import { checkMembershipGifting, useBanHammer } from '../ts/chat-actions'; import { mdiGift } from '@mdi/js'; export let message: Ytc.ParsedMessage; @@ -69,6 +70,13 @@ value: d.value.toString(), onClick: () => useBanHammer(message, d.value, $port) })); + + const triggerGiftCheck = () => { + if ($hasMembershipGiftingEnabled === null) { + checkMembershipGifting($port); + } + return true; + }; @@ -137,7 +145,7 @@ {forceTLColor} class={message.membershipGiftRedeem ? 'text-gray-700 dark:text-gray-600 italic font-medium' : ''} /> - {#if message.membershipGiftRedeem} + {#if message.membershipGiftRedeem && triggerGiftCheck()} => { + function getCookie(name: string): string { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return (parts.pop() ?? '').split(';').shift() ?? ''; + return ''; + } + const warning = 'HC button detected, not injecting interceptor.'; if (!isLiveTL && checkInjected(warning)) return; @@ -85,101 +92,106 @@ const chatLoaded = async (): Promise => { // eslint-disable-next-line @typescript-eslint/no-misused-promises port.onMessage.addListener(async (msg) => { - if (msg.type !== 'executeChatAction') return; - const message = msg.message; - if (message.params == null) return; - let success = true; - try { - // const action = msg.action; - const apiKey = ytcfg.data_.INNERTUBE_API_KEY; - const contextMenuUrl = 'https://www.youtube.com/youtubei/v1/live_chat/get_item_context_menu?params=' + - `${encodeURIComponent(message.params)}&pbj=1&key=${apiKey}&prettyPrint=false`; - const baseContext = ytcfg.data_.INNERTUBE_CONTEXT; - function getCookie(name: string): string { - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) return (parts.pop() ?? '').split(';').shift() ?? ''; - return ''; - } - const time = Math.floor(Date.now() / 1000); - const SAPISID = getCookie('__Secure-3PAPISID'); - const sha = sha1(`${time} ${SAPISID} https://www.youtube.com`); - const auth = `SAPISIDHASH ${time}_${sha}`; - const heads = { - headers: { - 'Content-Type': 'application/json', - Accept: '*/*', - Authorization: auth - }, - method: 'POST' - }; - const res = await fetcher(contextMenuUrl, { - ...heads, - body: JSON.stringify({ context: baseContext }) - }); - function parseServiceEndpoint(serviceEndpoint: any, prop: string): { params: string, context: any } { - const { clickTrackingParams, [prop]: { params } } = serviceEndpoint; - const clonedContext = JSON.parse(JSON.stringify(baseContext)); - clonedContext.clickTracking = { - clickTrackingParams - }; - return { - params, - context: clonedContext - }; - } - if (msg.action === ChatUserActions.BLOCK) { - const { params, context } = parseServiceEndpoint( - res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[1] - .menuNavigationItemRenderer.navigationEndpoint.confirmDialogEndpoint - .content.confirmDialogRenderer.confirmButton.buttonRenderer.serviceEndpoint, - 'moderateLiveChatEndpoint' - ); - await fetcher(`https://www.youtube.com/youtubei/v1/live_chat/moderate?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - params, - context - }) + switch (msg.type) { + case 'executeChatAction': { + const message = msg.message; + if (message.params == null) return; + let success = true; + try { + // const action = msg.action; + const apiKey = ytcfg.data_.INNERTUBE_API_KEY; + const contextMenuUrl = 'https://www.youtube.com/youtubei/v1/live_chat/get_item_context_menu?params=' + + `${encodeURIComponent(message.params)}&pbj=1&key=${apiKey}&prettyPrint=false`; + const baseContext = ytcfg.data_.INNERTUBE_CONTEXT; + const time = Math.floor(Date.now() / 1000); + const SAPISID = getCookie('__Secure-3PAPISID'); + const sha = sha1(`${time} ${SAPISID} https://www.youtube.com`); + const auth = `SAPISIDHASH ${time}_${sha}`; + const heads = { + headers: { + 'Content-Type': 'application/json', + Accept: '*/*', + Authorization: auth + }, + method: 'POST' + }; + const res = await fetcher(contextMenuUrl, { + ...heads, + body: JSON.stringify({ context: baseContext }) + }); + function parseServiceEndpoint(serviceEndpoint: any, prop: string): { params: string, context: any } { + const { clickTrackingParams, [prop]: { params } } = serviceEndpoint; + const clonedContext = JSON.parse(JSON.stringify(baseContext)); + clonedContext.clickTracking = { + clickTrackingParams + }; + return { + params, + context: clonedContext + }; + } + if (msg.action === ChatUserActions.BLOCK) { + const { params, context } = parseServiceEndpoint( + res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[1] + .menuNavigationItemRenderer.navigationEndpoint.confirmDialogEndpoint + .content.confirmDialogRenderer.confirmButton.buttonRenderer.serviceEndpoint, + 'moderateLiveChatEndpoint' + ); + await fetcher(`https://www.youtube.com/youtubei/v1/live_chat/moderate?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + params, + context + }) + }); + } else if (msg.action === ChatUserActions.REPORT_USER) { + const { params, context } = parseServiceEndpoint( + res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[0].menuServiceItemRenderer.serviceEndpoint, + 'getReportFormEndpoint' + ); + const modal = await fetcher(`https://www.youtube.com/youtubei/v1/flag/get_form?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + params, + context + }) + }); + const index = chatReportUserOptions.findIndex(d => d.value === msg.reportOption); + const options = modal.actions[0].openPopupAction.popup.reportFormModalRenderer.optionsSupportedRenderers.optionsRenderer.items; + const submitEndpoint = options[index].optionSelectableItemRenderer.submitEndpoint; + const clickTrackingParams = submitEndpoint.clickTrackingParams; + const flagAction = submitEndpoint.flagEndpoint.flagAction; + context.clickTracking = { + clickTrackingParams + }; + await fetcher(`https://www.youtube.com/youtubei/v1/flag/flag?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + action: flagAction, + context + }) + }); + } + } catch (e) { + console.debug('Error executing chat action', e); + success = false; + } + port.postMessage({ + type: 'chatUserActionResponse', + action: msg.action, + message, + success }); - } else if (msg.action === ChatUserActions.REPORT_USER) { - const { params, context } = parseServiceEndpoint( - res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[0].menuServiceItemRenderer.serviceEndpoint, - 'getReportFormEndpoint' - ); - const modal = await fetcher(`https://www.youtube.com/youtubei/v1/flag/get_form?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - params, - context - }) - }); - const index = chatReportUserOptions.findIndex(d => d.value === msg.reportOption); - const options = modal.actions[0].openPopupAction.popup.reportFormModalRenderer.optionsSupportedRenderers.optionsRenderer.items; - const submitEndpoint = options[index].optionSelectableItemRenderer.submitEndpoint; - const clickTrackingParams = submitEndpoint.clickTrackingParams; - const flagAction = submitEndpoint.flagEndpoint.flagAction; - context.clickTracking = { - clickTrackingParams - }; - await fetcher(`https://www.youtube.com/youtubei/v1/flag/flag?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - action: flagAction, - context - }) + break; + } + case 'checkMembershipGifting': { + port.postMessage({ + type: 'checkMembershipGiftingResponse', + result: false }); + break; } - } catch (e) { - console.debug('Error executing chat action', e); - success = false; } - port.postMessage({ - type: 'chatUserActionResponse', - action: msg.action, - message, - success - }); }); }); diff --git a/src/ts/chat-actions.ts b/src/ts/chat-actions.ts index aa5a3d47..834df507 100644 --- a/src/ts/chat-actions.ts +++ b/src/ts/chat-actions.ts @@ -28,3 +28,11 @@ export function useBanHammer( }); } } + +export function checkMembershipGifting( + port: Chat.Port | null +): void { + port?.postMessage({ + type: 'checkMembershipGifting' + }); +} diff --git a/src/ts/storage.ts b/src/ts/storage.ts index a09bd2bc..aa8dc704 100644 --- a/src/ts/storage.ts +++ b/src/ts/storage.ts @@ -79,3 +79,4 @@ export const currentProgress = writable(null as null | number); export const enableStickySuperchatBar = stores.addSyncStore('hc.enableStickySuperchatBar', true); export const enableHighlightedMentions = stores.addSyncStore('hc.enableHighlightedMentions', true); export const lastOpenedVersion = stores.addSyncStore('hc.lastOpenedVersion', ''); +export const hasMembershipGiftingEnabled = writable(null as null | boolean); diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index 76525eff..a5834677 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -76,9 +76,13 @@ declare namespace Chat { success: boolean; } + type InterceptorActions = + executeChatActionMsg | chatUserActionResponse | + checkMembershipGiftingMsg | checkMembershipGiftingResponse; + type BackgroundResponse = Actions | InitialData | ThemeUpdate | LtlMessageResponse | - registerClientResponse | executeChatActionMsg | chatUserActionResponse; + registerClientResponse | InterceptorActions; type InterceptorSource = 'ytc' | 'ltlMessage'; @@ -139,10 +143,19 @@ declare namespace Chat { reportOption?: ChatReportUserOptions; } + interface checkMembershipGiftingMsg { + type: 'checkMembershipGifting'; + } + + interface checkMembershipGiftingResponse { + type: 'checkMembershipGiftingResponse'; + result: boolean; + } + type BackgroundMessage = RegisterInterceptorMsg | RegisterClientMsg | processJsonMsg | setInitialDataMsg | updatePlayerProgressMsg | setThemeMsg | getThemeMsg | - RegisterYtcInterceptorMsg | sendLtlMessageMsg | executeChatActionMsg | chatUserActionResponse; + RegisterYtcInterceptorMsg | sendLtlMessageMsg | InterceptorActions; type Port = Omit & { postMessage: (message: BackgroundMessage | BackgroundResponse) => void; From b24c0dca710fc1ac9f4ce3de07b167471a1b514e Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Wed, 13 Jul 2022 23:34:46 -0700 Subject: [PATCH 02/16] undoing the things i did This reverts commit 580072b8ecce978b2e57e8a281f9e46e00d5521a. --- src/components/Message.svelte | 14 +-- src/scripts/chat-interceptor.ts | 194 +++++++++++++++----------------- src/ts/chat-actions.ts | 8 -- src/ts/storage.ts | 1 - src/ts/typings/chat.d.ts | 17 +-- 5 files changed, 96 insertions(+), 138 deletions(-) diff --git a/src/components/Message.svelte b/src/components/Message.svelte index 48821d99..e91fa515 100644 --- a/src/components/Message.svelte +++ b/src/components/Message.svelte @@ -9,11 +9,10 @@ showUserBadges, hoveredItem, port, - selfChannelId, - hasMembershipGiftingEnabled + selfChannelId } from '../ts/storage'; import { chatUserActionsItems, Theme } from '../ts/chat-constants'; - import { checkMembershipGifting, useBanHammer } from '../ts/chat-actions'; + import { useBanHammer } from '../ts/chat-actions'; import { mdiGift } from '@mdi/js'; export let message: Ytc.ParsedMessage; @@ -70,13 +69,6 @@ value: d.value.toString(), onClick: () => useBanHammer(message, d.value, $port) })); - - const triggerGiftCheck = () => { - if ($hasMembershipGiftingEnabled === null) { - checkMembershipGifting($port); - } - return true; - }; @@ -145,7 +137,7 @@ {forceTLColor} class={message.membershipGiftRedeem ? 'text-gray-700 dark:text-gray-600 italic font-medium' : ''} /> - {#if message.membershipGiftRedeem && triggerGiftCheck()} + {#if message.membershipGiftRedeem} => { - function getCookie(name: string): string { - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) return (parts.pop() ?? '').split(';').shift() ?? ''; - return ''; - } - const warning = 'HC button detected, not injecting interceptor.'; if (!isLiveTL && checkInjected(warning)) return; @@ -92,106 +85,101 @@ const chatLoaded = async (): Promise => { // eslint-disable-next-line @typescript-eslint/no-misused-promises port.onMessage.addListener(async (msg) => { - switch (msg.type) { - case 'executeChatAction': { - const message = msg.message; - if (message.params == null) return; - let success = true; - try { - // const action = msg.action; - const apiKey = ytcfg.data_.INNERTUBE_API_KEY; - const contextMenuUrl = 'https://www.youtube.com/youtubei/v1/live_chat/get_item_context_menu?params=' + - `${encodeURIComponent(message.params)}&pbj=1&key=${apiKey}&prettyPrint=false`; - const baseContext = ytcfg.data_.INNERTUBE_CONTEXT; - const time = Math.floor(Date.now() / 1000); - const SAPISID = getCookie('__Secure-3PAPISID'); - const sha = sha1(`${time} ${SAPISID} https://www.youtube.com`); - const auth = `SAPISIDHASH ${time}_${sha}`; - const heads = { - headers: { - 'Content-Type': 'application/json', - Accept: '*/*', - Authorization: auth - }, - method: 'POST' - }; - const res = await fetcher(contextMenuUrl, { - ...heads, - body: JSON.stringify({ context: baseContext }) - }); - function parseServiceEndpoint(serviceEndpoint: any, prop: string): { params: string, context: any } { - const { clickTrackingParams, [prop]: { params } } = serviceEndpoint; - const clonedContext = JSON.parse(JSON.stringify(baseContext)); - clonedContext.clickTracking = { - clickTrackingParams - }; - return { - params, - context: clonedContext - }; - } - if (msg.action === ChatUserActions.BLOCK) { - const { params, context } = parseServiceEndpoint( - res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[1] - .menuNavigationItemRenderer.navigationEndpoint.confirmDialogEndpoint - .content.confirmDialogRenderer.confirmButton.buttonRenderer.serviceEndpoint, - 'moderateLiveChatEndpoint' - ); - await fetcher(`https://www.youtube.com/youtubei/v1/live_chat/moderate?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - params, - context - }) - }); - } else if (msg.action === ChatUserActions.REPORT_USER) { - const { params, context } = parseServiceEndpoint( - res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[0].menuServiceItemRenderer.serviceEndpoint, - 'getReportFormEndpoint' - ); - const modal = await fetcher(`https://www.youtube.com/youtubei/v1/flag/get_form?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - params, - context - }) - }); - const index = chatReportUserOptions.findIndex(d => d.value === msg.reportOption); - const options = modal.actions[0].openPopupAction.popup.reportFormModalRenderer.optionsSupportedRenderers.optionsRenderer.items; - const submitEndpoint = options[index].optionSelectableItemRenderer.submitEndpoint; - const clickTrackingParams = submitEndpoint.clickTrackingParams; - const flagAction = submitEndpoint.flagEndpoint.flagAction; - context.clickTracking = { - clickTrackingParams - }; - await fetcher(`https://www.youtube.com/youtubei/v1/flag/flag?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - action: flagAction, - context - }) - }); - } - } catch (e) { - console.debug('Error executing chat action', e); - success = false; - } - port.postMessage({ - type: 'chatUserActionResponse', - action: msg.action, - message, - success - }); - break; + if (msg.type !== 'executeChatAction') return; + const message = msg.message; + if (message.params == null) return; + let success = true; + try { + // const action = msg.action; + const apiKey = ytcfg.data_.INNERTUBE_API_KEY; + const contextMenuUrl = 'https://www.youtube.com/youtubei/v1/live_chat/get_item_context_menu?params=' + + `${encodeURIComponent(message.params)}&pbj=1&key=${apiKey}&prettyPrint=false`; + const baseContext = ytcfg.data_.INNERTUBE_CONTEXT; + function getCookie(name: string): string { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return (parts.pop() ?? '').split(';').shift() ?? ''; + return ''; + } + const time = Math.floor(Date.now() / 1000); + const SAPISID = getCookie('__Secure-3PAPISID'); + const sha = sha1(`${time} ${SAPISID} https://www.youtube.com`); + const auth = `SAPISIDHASH ${time}_${sha}`; + const heads = { + headers: { + 'Content-Type': 'application/json', + Accept: '*/*', + Authorization: auth + }, + method: 'POST' + }; + const res = await fetcher(contextMenuUrl, { + ...heads, + body: JSON.stringify({ context: baseContext }) + }); + function parseServiceEndpoint(serviceEndpoint: any, prop: string): { params: string, context: any } { + const { clickTrackingParams, [prop]: { params } } = serviceEndpoint; + const clonedContext = JSON.parse(JSON.stringify(baseContext)); + clonedContext.clickTracking = { + clickTrackingParams + }; + return { + params, + context: clonedContext + }; } - case 'checkMembershipGifting': { - port.postMessage({ - type: 'checkMembershipGiftingResponse', - result: false + if (msg.action === ChatUserActions.BLOCK) { + const { params, context } = parseServiceEndpoint( + res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[1] + .menuNavigationItemRenderer.navigationEndpoint.confirmDialogEndpoint + .content.confirmDialogRenderer.confirmButton.buttonRenderer.serviceEndpoint, + 'moderateLiveChatEndpoint' + ); + await fetcher(`https://www.youtube.com/youtubei/v1/live_chat/moderate?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + params, + context + }) + }); + } else if (msg.action === ChatUserActions.REPORT_USER) { + const { params, context } = parseServiceEndpoint( + res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[0].menuServiceItemRenderer.serviceEndpoint, + 'getReportFormEndpoint' + ); + const modal = await fetcher(`https://www.youtube.com/youtubei/v1/flag/get_form?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + params, + context + }) + }); + const index = chatReportUserOptions.findIndex(d => d.value === msg.reportOption); + const options = modal.actions[0].openPopupAction.popup.reportFormModalRenderer.optionsSupportedRenderers.optionsRenderer.items; + const submitEndpoint = options[index].optionSelectableItemRenderer.submitEndpoint; + const clickTrackingParams = submitEndpoint.clickTrackingParams; + const flagAction = submitEndpoint.flagEndpoint.flagAction; + context.clickTracking = { + clickTrackingParams + }; + await fetcher(`https://www.youtube.com/youtubei/v1/flag/flag?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + action: flagAction, + context + }) }); - break; } + } catch (e) { + console.debug('Error executing chat action', e); + success = false; } + port.postMessage({ + type: 'chatUserActionResponse', + action: msg.action, + message, + success + }); }); }); diff --git a/src/ts/chat-actions.ts b/src/ts/chat-actions.ts index 834df507..aa5a3d47 100644 --- a/src/ts/chat-actions.ts +++ b/src/ts/chat-actions.ts @@ -28,11 +28,3 @@ export function useBanHammer( }); } } - -export function checkMembershipGifting( - port: Chat.Port | null -): void { - port?.postMessage({ - type: 'checkMembershipGifting' - }); -} diff --git a/src/ts/storage.ts b/src/ts/storage.ts index aa8dc704..a09bd2bc 100644 --- a/src/ts/storage.ts +++ b/src/ts/storage.ts @@ -79,4 +79,3 @@ export const currentProgress = writable(null as null | number); export const enableStickySuperchatBar = stores.addSyncStore('hc.enableStickySuperchatBar', true); export const enableHighlightedMentions = stores.addSyncStore('hc.enableHighlightedMentions', true); export const lastOpenedVersion = stores.addSyncStore('hc.lastOpenedVersion', ''); -export const hasMembershipGiftingEnabled = writable(null as null | boolean); diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index a5834677..76525eff 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -76,13 +76,9 @@ declare namespace Chat { success: boolean; } - type InterceptorActions = - executeChatActionMsg | chatUserActionResponse | - checkMembershipGiftingMsg | checkMembershipGiftingResponse; - type BackgroundResponse = Actions | InitialData | ThemeUpdate | LtlMessageResponse | - registerClientResponse | InterceptorActions; + registerClientResponse | executeChatActionMsg | chatUserActionResponse; type InterceptorSource = 'ytc' | 'ltlMessage'; @@ -143,19 +139,10 @@ declare namespace Chat { reportOption?: ChatReportUserOptions; } - interface checkMembershipGiftingMsg { - type: 'checkMembershipGifting'; - } - - interface checkMembershipGiftingResponse { - type: 'checkMembershipGiftingResponse'; - result: boolean; - } - type BackgroundMessage = RegisterInterceptorMsg | RegisterClientMsg | processJsonMsg | setInitialDataMsg | updatePlayerProgressMsg | setThemeMsg | getThemeMsg | - RegisterYtcInterceptorMsg | sendLtlMessageMsg | InterceptorActions; + RegisterYtcInterceptorMsg | sendLtlMessageMsg | executeChatActionMsg | chatUserActionResponse; type Port = Omit & { postMessage: (message: BackgroundMessage | BackgroundResponse) => void; From ade691154046699784f70945030da06f0acd9c78 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 14 Jul 2022 00:27:53 -0700 Subject: [PATCH 03/16] fix missing superchats when emoji filtering is on --- src/ts/chat-utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ts/chat-utils.ts b/src/ts/chat-utils.ts index 6fa4fa8d..cb793783 100644 --- a/src/ts/chat-utils.ts +++ b/src/ts/chat-utils.ts @@ -51,6 +51,7 @@ export const isChatMessage = (a: Chat.MessageAction): boolean => !a.message.superChat && !a.message.superSticker && !a.message.membership; export const isAllEmoji = (a: Chat.MessageAction): boolean => + a.message.message.some(m => m.type === 'text') && a.message.message.every(m => m.type === 'emoji' || (m.type === 'text' && m.text.trim() === '')); export const checkInjected = (error: string): boolean => { From b0dfd2838cf05670b384a9a57e36cd805e029d77 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 14 Jul 2022 00:39:45 -0700 Subject: [PATCH 04/16] "I am stupid" - Charles Leclerc --- src/ts/chat-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ts/chat-utils.ts b/src/ts/chat-utils.ts index cb793783..33a1d93b 100644 --- a/src/ts/chat-utils.ts +++ b/src/ts/chat-utils.ts @@ -51,7 +51,7 @@ export const isChatMessage = (a: Chat.MessageAction): boolean => !a.message.superChat && !a.message.superSticker && !a.message.membership; export const isAllEmoji = (a: Chat.MessageAction): boolean => - a.message.message.some(m => m.type === 'text') && + a.message.message.length !== 0 && a.message.message.every(m => m.type === 'emoji' || (m.type === 'text' && m.text.trim() === '')); export const checkInjected = (error: string): boolean => { From 1fdf9c13fa711e9dc82a8974bbdb8cc0c7f521b2 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Wed, 27 Jul 2022 23:20:25 -0700 Subject: [PATCH 05/16] start work on toggleMembershipGifting --- src/scripts/chat-interceptor.ts | 235 ++++++++++++++++++++------------ src/ts/typings/chat.d.ts | 32 +++-- src/ts/typings/ytc.d.ts | 28 ++++ 3 files changed, 202 insertions(+), 93 deletions(-) diff --git a/src/scripts/chat-interceptor.ts b/src/scripts/chat-interceptor.ts index 9ed720bb..61aff7b7 100644 --- a/src/scripts/chat-interceptor.ts +++ b/src/scripts/chat-interceptor.ts @@ -85,101 +85,166 @@ const chatLoaded = async (): Promise => { // eslint-disable-next-line @typescript-eslint/no-misused-promises port.onMessage.addListener(async (msg) => { - if (msg.type !== 'executeChatAction') return; - const message = msg.message; - if (message.params == null) return; let success = true; - try { - // const action = msg.action; - const apiKey = ytcfg.data_.INNERTUBE_API_KEY; - const contextMenuUrl = 'https://www.youtube.com/youtubei/v1/live_chat/get_item_context_menu?params=' + - `${encodeURIComponent(message.params)}&pbj=1&key=${apiKey}&prettyPrint=false`; - const baseContext = ytcfg.data_.INNERTUBE_CONTEXT; - function getCookie(name: string): string { - const value = `; ${document.cookie}`; - const parts = value.split(`; ${name}=`); - if (parts.length === 2) return (parts.pop() ?? '').split(';').shift() ?? ''; - return ''; - } - const time = Math.floor(Date.now() / 1000); - const SAPISID = getCookie('__Secure-3PAPISID'); - const sha = sha1(`${time} ${SAPISID} https://www.youtube.com`); - const auth = `SAPISIDHASH ${time}_${sha}`; - const heads = { - headers: { - 'Content-Type': 'application/json', - Accept: '*/*', - Authorization: auth - }, - method: 'POST' + const apiKey = ytcfg.data_.INNERTUBE_API_KEY; + const baseContext = ytcfg.data_.INNERTUBE_CONTEXT; + function getCookie(name: string): string { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return (parts.pop() ?? '').split(';').shift() ?? ''; + return ''; + } + const time = Math.floor(Date.now() / 1000); + const SAPISID = getCookie('__Secure-3PAPISID'); + const sha = sha1(`${time} ${SAPISID} https://www.youtube.com`); + const auth = `SAPISIDHASH ${time}_${sha}`; + const heads = { + headers: { + 'Content-Type': 'application/json', + Accept: '*/*', + Authorization: auth + }, + method: 'POST' + }; + function parseServiceEndpoint(serviceEndpoint: any, prop: string): { params: string, context: any } { + const { clickTrackingParams, [prop]: { params } } = serviceEndpoint; + const clonedContext = JSON.parse(JSON.stringify(baseContext)); + clonedContext.clickTracking = { + clickTrackingParams }; - const res = await fetcher(contextMenuUrl, { - ...heads, - body: JSON.stringify({ context: baseContext }) - }); - function parseServiceEndpoint(serviceEndpoint: any, prop: string): { params: string, context: any } { - const { clickTrackingParams, [prop]: { params } } = serviceEndpoint; - const clonedContext = JSON.parse(JSON.stringify(baseContext)); - clonedContext.clickTracking = { - clickTrackingParams - }; - return { - params, - context: clonedContext - }; - } - if (msg.action === ChatUserActions.BLOCK) { - const { params, context } = parseServiceEndpoint( - res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[1] - .menuNavigationItemRenderer.navigationEndpoint.confirmDialogEndpoint - .content.confirmDialogRenderer.confirmButton.buttonRenderer.serviceEndpoint, - 'moderateLiveChatEndpoint' - ); - await fetcher(`https://www.youtube.com/youtubei/v1/live_chat/moderate?key=${apiKey}&prettyPrint=false`, { + return { + params, + context: clonedContext + }; + } + if (msg.type === 'executeChatAction') { + const message = msg.message; + if (message.params == null) return; + try { + // const action = msg.action; + const contextMenuUrl = 'https://www.youtube.com/youtubei/v1/live_chat/get_item_context_menu?params=' + + `${encodeURIComponent(message.params)}&pbj=1&key=${apiKey}&prettyPrint=false`; + const res = await fetcher(contextMenuUrl, { ...heads, - body: JSON.stringify({ - params, - context - }) + body: JSON.stringify({ context: baseContext }) }); - } else if (msg.action === ChatUserActions.REPORT_USER) { - const { params, context } = parseServiceEndpoint( - res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[0].menuServiceItemRenderer.serviceEndpoint, - 'getReportFormEndpoint' - ); - const modal = await fetcher(`https://www.youtube.com/youtubei/v1/flag/get_form?key=${apiKey}&prettyPrint=false`, { + if (msg.action === ChatUserActions.BLOCK) { + const { params, context } = parseServiceEndpoint( + res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[1] + .menuNavigationItemRenderer.navigationEndpoint.confirmDialogEndpoint + .content.confirmDialogRenderer.confirmButton.buttonRenderer.serviceEndpoint, + 'moderateLiveChatEndpoint' + ); + await fetcher(`https://www.youtube.com/youtubei/v1/live_chat/moderate?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + params, + context + }) + }); + } else if (msg.action === ChatUserActions.REPORT_USER) { + const { params, context } = parseServiceEndpoint( + res.liveChatItemContextMenuSupportedRenderers.menuRenderer.items[0].menuServiceItemRenderer.serviceEndpoint, + 'getReportFormEndpoint' + ); + const modal = await fetcher(`https://www.youtube.com/youtubei/v1/flag/get_form?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + params, + context + }) + }); + const index = chatReportUserOptions.findIndex(d => d.value === msg.reportOption); + const options = modal.actions[0].openPopupAction.popup.reportFormModalRenderer.optionsSupportedRenderers.optionsRenderer.items; + const submitEndpoint = options[index].optionSelectableItemRenderer.submitEndpoint; + const clickTrackingParams = submitEndpoint.clickTrackingParams; + const flagAction = submitEndpoint.flagEndpoint.flagAction; + context.clickTracking = { + clickTrackingParams + }; + await fetcher(`https://www.youtube.com/youtubei/v1/flag/flag?key=${apiKey}&prettyPrint=false`, { + ...heads, + body: JSON.stringify({ + action: flagAction, + context + }) + }); + } + } catch (e) { + console.debug('Error executing chat action', e); + success = false; + } + port.postMessage({ + type: 'chatUserActionResponse', + action: msg.action, + message, + success + }); + } else if (msg.type === 'toggleMembershipGifting') { + let currentValue: boolean = false; + try { + const renderer = msg.renderer; + if (!renderer.optInPrompt || renderer.optInPrompt.buttonRenderer.isDisabled) return; + const d = renderer.optInPrompt.buttonRenderer; + const { params, context } = parseServiceEndpoint(d.command, 'browseEndpoint'); + const popup = await (await fetch(`https://www.youtube.com/youtubei/v1/browse?key=${apiKey}&prettyPrint=false`, { ...heads, body: JSON.stringify({ + context, params, - context - }) - }); - const index = chatReportUserOptions.findIndex(d => d.value === msg.reportOption); - const options = modal.actions[0].openPopupAction.popup.reportFormModalRenderer.optionsSupportedRenderers.optionsRenderer.items; - const submitEndpoint = options[index].optionSelectableItemRenderer.submitEndpoint; - const clickTrackingParams = submitEndpoint.clickTrackingParams; - const flagAction = submitEndpoint.flagEndpoint.flagAction; - context.clickTracking = { - clickTrackingParams - }; - await fetcher(`https://www.youtube.com/youtubei/v1/flag/flag?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - action: flagAction, - context + browseId: d.command.browseEndpoint.browseId }) - }); + })).json(); + interface optCommand { + clickTrackingParams: string; + commandMetaData: { + webCommandMetadata: { + apiUrl: string; + sendPost: boolean; + }; + }; + feedbackEndpoint: { + feedbackToken: string; + }; + } + const optRenderer = popup.onResponseReceivedActions[0].openPopupAction.popup.sponsorshipsGiftingOptInRenderer; + currentValue = optRenderer.initialOptInStatus === 'SPONSORSHIPS_GIFTING_OPT_IN_STATUS_ENABLED'; + if (msg.readonly) { + port.postMessage({ + type: 'toggleMembershipGiftingResponse', + success: true, + enabled: currentValue + }); + } else { + const { optInCommand, optOutCommand }: { + optInCommand: optCommand; + optOutCommand: optCommand; + } = optRenderer; + const toggle = currentValue ? optOutCommand : optInCommand; + const res = await (await fetcher(toggle.commandMetaData.webCommandMetadata.apiUrl, { + ...heads, + body: JSON.stringify({ + context, + feedbackToken: { + 0: toggle.feedbackEndpoint.feedbackToken + }, + isFeedbackTokenUnencrypted: false, + shouldMerge: false + }) + })).json(); + success = res.feedbackResponse[0].isProcessed as boolean; + if (success) currentValue = !currentValue; + } + } catch (e) { + console.debug('Error executing toggle membership gifting', e); + success = false; } - } catch (e) { - console.debug('Error executing chat action', e); - success = false; + port.postMessage({ + type: 'toggleMembershipGiftingResponse', + success, + enabled: currentValue + }); } - port.postMessage({ - type: 'chatUserActionResponse', - action: msg.action, - message, - success - }); }); }); diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index 76525eff..a0316acd 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -69,16 +69,13 @@ declare namespace Chat { failReason?: string; } - interface chatUserActionResponse { - type: 'chatUserActionResponse'; - action: ChatUserActions; - message: Ytc.ParsedMessage; - success: boolean; - } + type interceptorCommunication = + executeChatActionMsg | chatUserActionResponse | + toggleMembershipGiftingMsg | toggleMembershipGiftingResponse; type BackgroundResponse = Actions | InitialData | ThemeUpdate | LtlMessageResponse | - registerClientResponse | executeChatActionMsg | chatUserActionResponse; + registerClientResponse | interceptorCommunication; type InterceptorSource = 'ytc' | 'ltlMessage'; @@ -139,10 +136,29 @@ declare namespace Chat { reportOption?: ChatReportUserOptions; } + interface chatUserActionResponse { + type: 'chatUserActionResponse'; + action: ChatUserActions; + message: Ytc.ParsedMessage; + success: boolean; + } + + interface toggleMembershipGiftingMsg { + type: 'toggleMembershipGifting'; + renderer: Ytc.MembershipGiftPurchaseRenderer; + readonly: boolean; + } + + interface toggleMembershipGiftingResponse { + type: 'toggleMembershipGiftingResponse'; + success: boolean; + enabled: boolean; + } + type BackgroundMessage = RegisterInterceptorMsg | RegisterClientMsg | processJsonMsg | setInitialDataMsg | updatePlayerProgressMsg | setThemeMsg | getThemeMsg | - RegisterYtcInterceptorMsg | sendLtlMessageMsg | executeChatActionMsg | chatUserActionResponse; + RegisterYtcInterceptorMsg | sendLtlMessageMsg | interceptorCommunication; type Port = Omit & { postMessage: (message: BackgroundMessage | BackgroundResponse) => void; diff --git a/src/ts/typings/ytc.d.ts b/src/ts/typings/ytc.d.ts index 0674c419..98415741 100644 --- a/src/ts/typings/ytc.d.ts +++ b/src/ts/typings/ytc.d.ts @@ -225,6 +225,34 @@ declare namespace Ytc { image: Thumbnails; }; }; + optInPrompt?: { + buttonRenderer: { + style: string; + size: string; + isDisabled: boolean; + text: { + runs: RunsObj; + }; + icon: { + iconType: string; + }; + trackingParams: string; + command: { + clickTrackingParams: string; + commandMetadata: { + webCommandMetadata: { + sendPost: boolean; + apiUrl: string; + }; + }; + browseEndpoint: { + browseId: string; + params: string; + navigationType: string; + }; + }; + }; + }; } interface PlaceholderRenderer { // No idea what the purpose of this is From b8217b0ac596beea47982ae188edbf9d2a342b77 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Wed, 27 Jul 2022 23:52:24 -0700 Subject: [PATCH 06/16] test data --- src/components/MembershipItem.svelte | 46 ++++++++++++++++++++++++++++ src/ts/chat-parser.ts | 3 +- src/ts/typings/ytc.d.ts | 3 +- 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index 860a7162..ee5d4e52 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -3,6 +3,7 @@ import MessageRun from './MessageRuns.svelte'; import { showProfileIcons } from '../ts/storage'; import { membershipBackground, milestoneChatBackground } from '../ts/chat-constants'; + import { mdiInformation } from '@mdi/js'; export let message: Ytc.ParsedMessage; @@ -16,6 +17,39 @@ $: isMilestoneChat = message.message.length > 0; $: primaryText = (membership || membershipGift)?.headerPrimaryText; + $: optInPrompt = { + buttonRenderer: { + style: 'STYLE_TEXT', + size: 'SIZE_DEFAULT', + isDisabled: false, + text: { + runs: [ + { + text: 'Allow Gifts' + } + ] + }, + icon: { + iconType: 'GIFT' + }, + trackingParams: 'CBsQ6P8IIhMInMjxqNCa-QIVwSqtBh17WA1Q', + command: { + clickTrackingParams: 'CBsQ6P8IIhMInMjxqNCa-QIVwSqtBh17WA1Q', + commandMetadata: { + webCommandMetadata: { + sendPost: true, + apiUrl: '/youtubei/v1/browse' + } + }, + browseEndpoint: { + browseId: 'FEgifting_opt_in', + params: 'igcaChhVQzNuNXVHdTE4Rm9DeTIzZ2dXV3A4dEE%3D', + navigationType: 'BROWSE_NAVIGATION_TYPE_STAY_ON_PAGE' + } + } + } + }; + // membershipGift?.optInPrompt; {#if membership || membershipGift} @@ -49,6 +83,18 @@ src={membershipGift.image.src} alt={membershipGift.image.alt} title={membershipGift.image.alt} /> + {#if optInPrompt} +
+ + + + + + Opt into membership gift reception for this channel + + +
+ {/if} {/if} {#if isMilestoneChat} diff --git a/src/ts/chat-parser.ts b/src/ts/chat-parser.ts index 2a1e6788..40e0c951 100644 --- a/src/ts/chat-parser.ts +++ b/src/ts/chat-parser.ts @@ -160,7 +160,8 @@ const parseAddChatItemAction = (action: Ytc.AddChatItemAction, isReplay = false, image: { src: fixUrl(header.image.thumbnails[0].url), alt: 'gift' - } + }, + optInPrompt: renderer.optInPrompt }; } else if (actionItem.liveChatSponsorshipsGiftRedemptionAnnouncementRenderer) { item.membershipGiftRedeem = true; diff --git a/src/ts/typings/ytc.d.ts b/src/ts/typings/ytc.d.ts index 98415741..64f9ce25 100644 --- a/src/ts/typings/ytc.d.ts +++ b/src/ts/typings/ytc.d.ts @@ -231,7 +231,7 @@ declare namespace Ytc { size: string; isDisabled: boolean; text: { - runs: RunsObj; + runs: Array<{text: string}>; }; icon: { iconType: string; @@ -352,6 +352,7 @@ declare namespace Ytc { interface ParsedMembershipGiftPurchase { headerPrimaryText: ParsedRun[]; image: ParsedImage; + optInPrompt?: MembershipGiftPurchaseRenderer['optInPrompt']; } interface ParsedMessage { From 498883dd31f283a4bcef05937f61858ebd5c56b9 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 28 Jul 2022 00:35:31 -0700 Subject: [PATCH 07/16] toggleMembershipGiftingResponse --- src/components/Hyperchat.svelte | 6 ++- src/components/MembershipItem.svelte | 8 +++- src/scripts/chat-background.ts | 26 ++++++++++++ src/scripts/chat-interceptor.ts | 16 +++----- src/ts/chat-actions.ts | 11 +++++ src/ts/storage.ts | 1 + src/ts/typings/chat.d.ts | 2 +- src/ts/typings/ytc.d.ts | 60 ++++++++++++++-------------- 8 files changed, 86 insertions(+), 44 deletions(-) diff --git a/src/components/Hyperchat.svelte b/src/components/Hyperchat.svelte index e178078d..40f068e6 100644 --- a/src/components/Hyperchat.svelte +++ b/src/components/Hyperchat.svelte @@ -40,7 +40,8 @@ enableStickySuperchatBar, lastOpenedVersion, selfChannelName, - enableHighlightedMentions + enableHighlightedMentions, + membershipGiftingEnabledOnChannel } from '../ts/storage'; import { version } from '../manifest.json'; @@ -235,6 +236,9 @@ ); } break; + case 'toggleMembershipGiftingResponse': + if (response.success) $membershipGiftingEnabledOnChannel = response.enabled; + break; case 'registerClientResponse': break; default: diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index ee5d4e52..931e6c4e 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -1,9 +1,10 @@ {#if membership || membershipGift} @@ -83,7 +87,7 @@ src={membershipGift.image.src} alt={membershipGift.image.alt} title={membershipGift.image.alt} /> - {#if optInPrompt} + {#if optInPrompt && $membershipGiftingEnabledOnChannel === false}
diff --git a/src/scripts/chat-background.ts b/src/scripts/chat-background.ts index 2bd6a822..f7a2ca70 100644 --- a/src/scripts/chat-background.ts +++ b/src/scripts/chat-background.ts @@ -379,6 +379,26 @@ const sendChatUserActionResponse = ( ); }; +const toggleMembershipGifting = ( + port: Chat.Port, + message: Chat.toggleMembershipGiftingMsg +): void => { + const interceptor = findInterceptorFromClient(port); + interceptor?.port?.postMessage(message); +}; + +const sendMembershipGiftingResponse = ( + port: Chat.Port, + message: Chat.toggleMembershipGiftingResponse +): void => { + const interceptor = findInterceptorFromPort(port, { message }); + if (!interceptor) return; + + interceptor.clients.forEach( + (clientPort) => clientPort.postMessage(message) + ); +}; + chrome.runtime.onConnect.addListener((port) => { port.onMessage.addListener((message: Chat.BackgroundMessage) => { switch (message.type) { @@ -415,6 +435,12 @@ chrome.runtime.onConnect.addListener((port) => { case 'chatUserActionResponse': sendChatUserActionResponse(port, message); break; + case 'toggleMembershipGifting': + toggleMembershipGifting(port, message); + break; + case 'toggleMembershipGiftingResponse': + sendMembershipGiftingResponse(port, message); + break; default: console.error('Unknown message type', port, message); break; diff --git a/src/scripts/chat-interceptor.ts b/src/scripts/chat-interceptor.ts index 61aff7b7..26d084a7 100644 --- a/src/scripts/chat-interceptor.ts +++ b/src/scripts/chat-interceptor.ts @@ -181,11 +181,10 @@ const chatLoaded = async (): Promise => { success }); } else if (msg.type === 'toggleMembershipGifting') { - let currentValue: boolean = false; + let currentValue = false; try { - const renderer = msg.renderer; - if (!renderer.optInPrompt || renderer.optInPrompt.buttonRenderer.isDisabled) return; - const d = renderer.optInPrompt.buttonRenderer; + const d = msg.prompt.buttonRenderer; + if (d.isDisabled) return; const { params, context } = parseServiceEndpoint(d.command, 'browseEndpoint'); const popup = await (await fetch(`https://www.youtube.com/youtubei/v1/browse?key=${apiKey}&prettyPrint=false`, { ...heads, @@ -209,13 +208,7 @@ const chatLoaded = async (): Promise => { } const optRenderer = popup.onResponseReceivedActions[0].openPopupAction.popup.sponsorshipsGiftingOptInRenderer; currentValue = optRenderer.initialOptInStatus === 'SPONSORSHIPS_GIFTING_OPT_IN_STATUS_ENABLED'; - if (msg.readonly) { - port.postMessage({ - type: 'toggleMembershipGiftingResponse', - success: true, - enabled: currentValue - }); - } else { + if (!msg.readonly) { const { optInCommand, optOutCommand }: { optInCommand: optCommand; optOutCommand: optCommand; @@ -244,6 +237,7 @@ const chatLoaded = async (): Promise => { success, enabled: currentValue }); + console.log('toggleMembershipGiftingResponse', 'success:', success, 'enabled:', currentValue); } }); }); diff --git a/src/ts/chat-actions.ts b/src/ts/chat-actions.ts index aa5a3d47..c78a7ebe 100644 --- a/src/ts/chat-actions.ts +++ b/src/ts/chat-actions.ts @@ -28,3 +28,14 @@ export function useBanHammer( }); } } + +export function fetchMembershipGifting( + prompt: Ytc.OptInPrompt, + port: Chat.Port | null +): void { + port?.postMessage({ + type: 'toggleMembershipGifting', + prompt, + readonly: true + }); +} diff --git a/src/ts/storage.ts b/src/ts/storage.ts index a09bd2bc..30d46c8c 100644 --- a/src/ts/storage.ts +++ b/src/ts/storage.ts @@ -79,3 +79,4 @@ export const currentProgress = writable(null as null | number); export const enableStickySuperchatBar = stores.addSyncStore('hc.enableStickySuperchatBar', true); export const enableHighlightedMentions = stores.addSyncStore('hc.enableHighlightedMentions', true); export const lastOpenedVersion = stores.addSyncStore('hc.lastOpenedVersion', ''); +export const membershipGiftingEnabledOnChannel = writable(null as null | boolean); diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index a0316acd..e526b10b 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -145,7 +145,7 @@ declare namespace Chat { interface toggleMembershipGiftingMsg { type: 'toggleMembershipGifting'; - renderer: Ytc.MembershipGiftPurchaseRenderer; + prompt: OptInPrompt; readonly: boolean; } diff --git a/src/ts/typings/ytc.d.ts b/src/ts/typings/ytc.d.ts index 64f9ce25..9a962949 100644 --- a/src/ts/typings/ytc.d.ts +++ b/src/ts/typings/ytc.d.ts @@ -218,6 +218,35 @@ declare namespace Ytc { headerSubtext: SimpleTextObj | RunsObj; } + interface OptInPrompt { + buttonRenderer: { + style: string; + size: string; + isDisabled: boolean; + text: { + runs: Array<{text: string}>; + }; + icon: { + iconType: string; + }; + trackingParams: string; + command: { + clickTrackingParams: string; + commandMetadata: { + webCommandMetadata: { + sendPost: boolean; + apiUrl: string; + }; + }; + browseEndpoint: { + browseId: string; + params: string; + navigationType: string; + }; + }; + }; + } + interface MembershipGiftPurchaseRenderer extends IRenderer { header: { liveChatSponsorshipsHeaderRenderer: TextMessageRenderer & { @@ -225,34 +254,7 @@ declare namespace Ytc { image: Thumbnails; }; }; - optInPrompt?: { - buttonRenderer: { - style: string; - size: string; - isDisabled: boolean; - text: { - runs: Array<{text: string}>; - }; - icon: { - iconType: string; - }; - trackingParams: string; - command: { - clickTrackingParams: string; - commandMetadata: { - webCommandMetadata: { - sendPost: boolean; - apiUrl: string; - }; - }; - browseEndpoint: { - browseId: string; - params: string; - navigationType: string; - }; - }; - }; - }; + optInPrompt?: OptInPrompt; } interface PlaceholderRenderer { // No idea what the purpose of this is @@ -352,7 +354,7 @@ declare namespace Ytc { interface ParsedMembershipGiftPurchase { headerPrimaryText: ParsedRun[]; image: ParsedImage; - optInPrompt?: MembershipGiftPurchaseRenderer['optInPrompt']; + optInPrompt?: OptInPrompt; } interface ParsedMessage { From 67e93b49b8058b7d72ae19168e9465f79f1d8e7f Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 28 Jul 2022 01:02:35 -0700 Subject: [PATCH 08/16] use actual prompt data ig --- src/components/MembershipItem.svelte | 43 +++++----------------------- src/ts/chat-actions.ts | 7 +++-- 2 files changed, 11 insertions(+), 39 deletions(-) diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index 931e6c4e..926e9b6a 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -4,7 +4,7 @@ import { showProfileIcons, membershipGiftingEnabledOnChannel, port } from '../ts/storage'; import { membershipBackground, milestoneChatBackground } from '../ts/chat-constants'; import { mdiInformation } from '@mdi/js'; - import { fetchMembershipGifting } from '../ts/chat-actions'; + import { fetchOrToggleMembershipGifting } from '../ts/chat-actions'; export let message: Ytc.ParsedMessage; @@ -18,41 +18,10 @@ $: isMilestoneChat = message.message.length > 0; $: primaryText = (membership || membershipGift)?.headerPrimaryText; - $: optInPrompt = { - buttonRenderer: { - style: 'STYLE_TEXT', - size: 'SIZE_DEFAULT', - isDisabled: false, - text: { - runs: [ - { - text: 'Allow Gifts' - } - ] - }, - icon: { - iconType: 'GIFT' - }, - trackingParams: 'CBsQ6P8IIhMInMjxqNCa-QIVwSqtBh17WA1Q', - command: { - clickTrackingParams: 'CBsQ6P8IIhMInMjxqNCa-QIVwSqtBh17WA1Q', - commandMetadata: { - webCommandMetadata: { - sendPost: true, - apiUrl: '/youtubei/v1/browse' - } - }, - browseEndpoint: { - browseId: 'FEgifting_opt_in', - params: 'igcaChhVQzNuNXVHdTE4Rm9DeTIzZ2dXV3A4dEE%3D', - navigationType: 'BROWSE_NAVIGATION_TYPE_STAY_ON_PAGE' - } - } - } - }; - // membershipGift?.optInPrompt; + $: optInPrompt = membershipGift?.optInPrompt; + // ; $: if (optInPrompt && $membershipGiftingEnabledOnChannel === null) { - fetchMembershipGifting(optInPrompt, $port); + fetchOrToggleMembershipGifting(optInPrompt, $port, false); } @@ -88,7 +57,9 @@ alt={membershipGift.image.alt} title={membershipGift.image.alt} /> {#if optInPrompt && $membershipGiftingEnabledOnChannel === false} -
+
{ + if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, true); + }}> diff --git a/src/ts/chat-actions.ts b/src/ts/chat-actions.ts index c78a7ebe..1469d5d5 100644 --- a/src/ts/chat-actions.ts +++ b/src/ts/chat-actions.ts @@ -29,13 +29,14 @@ export function useBanHammer( } } -export function fetchMembershipGifting( +export function fetchOrToggleMembershipGifting( prompt: Ytc.OptInPrompt, - port: Chat.Port | null + port: Chat.Port | null, + toggle: boolean ): void { port?.postMessage({ type: 'toggleMembershipGifting', prompt, - readonly: true + readonly: !toggle }); } From c34dcd841c3c333f4a415fa110ead0cc9e7642c0 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 28 Jul 2022 12:15:29 -0700 Subject: [PATCH 09/16] extract TransparentDialog.svelte --- src/components/MembershipItem.svelte | 34 +++++++++++++++++-- src/components/SuperchatViewDialog.svelte | 17 ++-------- .../common/TransparentDialog.svelte | 19 +++++++++++ 3 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 src/components/common/TransparentDialog.svelte diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index 926e9b6a..ab5136f9 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -18,8 +18,38 @@ $: isMilestoneChat = message.message.length > 0; $: primaryText = (membership || membershipGift)?.headerPrimaryText; - $: optInPrompt = membershipGift?.optInPrompt; - // ; + $: optInPrompt = membershipGift?.optInPrompt || { + buttonRenderer: { + style: 'STYLE_TEXT', + size: 'SIZE_DEFAULT', + isDisabled: false, + text: { + runs: [ + { + text: 'Allow Gifts' + } + ] + }, + icon: { + iconType: 'GIFT' + }, + trackingParams: 'CBsQ6P8IIhMInMjxqNCa-QIVwSqtBh17WA1Q', + command: { + clickTrackingParams: 'CBsQ6P8IIhMInMjxqNCa-QIVwSqtBh17WA1Q', + commandMetadata: { + webCommandMetadata: { + sendPost: true, + apiUrl: '/youtubei/v1/browse' + } + }, + browseEndpoint: { + browseId: 'FEgifting_opt_in', + params: 'igcaChhVQzNuNXVHdTE4Rm9DeTIzZ2dXV3A4dEE%3D', + navigationType: 'BROWSE_NAVIGATION_TYPE_STAY_ON_PAGE' + } + } + } +}; $: if (optInPrompt && $membershipGiftingEnabledOnChannel === null) { fetchOrToggleMembershipGifting(optInPrompt, $port, false); } diff --git a/src/components/SuperchatViewDialog.svelte b/src/components/SuperchatViewDialog.svelte index 2b07c6ed..a5af4169 100644 --- a/src/components/SuperchatViewDialog.svelte +++ b/src/components/SuperchatViewDialog.svelte @@ -2,7 +2,7 @@ import { focusedSuperchat } from '../ts/storage'; - import Dialog from './common/Dialog.svelte'; + import TransparentDialog from './common/TransparentDialog.svelte'; import PaidMessage from './PaidMessage.svelte'; import MembershipItem from './MembershipItem.svelte'; @@ -14,21 +14,10 @@ $: if (!open) closeDialog(); - + {#if ('superChat' in sc || 'superSticker' in sc)} {:else} {/if} - - - + diff --git a/src/components/common/TransparentDialog.svelte b/src/components/common/TransparentDialog.svelte new file mode 100644 index 00000000..6170b8de --- /dev/null +++ b/src/components/common/TransparentDialog.svelte @@ -0,0 +1,19 @@ + + + + + + + From 961180058402a80df3a6b3b943ee887ea0b33cba Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 28 Jul 2022 12:33:02 -0700 Subject: [PATCH 10/16] more progress --- src/components/AlertDialog.svelte | 18 ++++++++++++++++++ src/components/GiftedMembershipToggle.svelte | 0 src/components/Hyperchat.svelte | 11 +++++++++++ src/components/ReportBanDialog.svelte | 15 +-------------- 4 files changed, 30 insertions(+), 14 deletions(-) create mode 100644 src/components/AlertDialog.svelte create mode 100644 src/components/GiftedMembershipToggle.svelte diff --git a/src/components/AlertDialog.svelte b/src/components/AlertDialog.svelte new file mode 100644 index 00000000..fbf71759 --- /dev/null +++ b/src/components/AlertDialog.svelte @@ -0,0 +1,18 @@ + + + {$alertDialog?.title} +
+ {$alertDialog?.message} +
+
+ +
+ diff --git a/src/components/GiftedMembershipToggle.svelte b/src/components/GiftedMembershipToggle.svelte new file mode 100644 index 00000000..e69de29b diff --git a/src/components/Hyperchat.svelte b/src/components/Hyperchat.svelte index 40f068e6..b0cb1c94 100644 --- a/src/components/Hyperchat.svelte +++ b/src/components/Hyperchat.svelte @@ -9,6 +9,7 @@ import PaidMessage from './PaidMessage.svelte'; import MembershipItem from './MembershipItem.svelte'; import ReportBanDialog from './ReportBanDialog.svelte'; + import AlertDialog from './AlertDialog.svelte'; import SuperchatViewDialog from './SuperchatViewDialog.svelte'; import StickyBar from './StickyBar.svelte'; import { @@ -238,6 +239,15 @@ break; case 'toggleMembershipGiftingResponse': if (response.success) $membershipGiftingEnabledOnChannel = response.enabled; + else { + $alertDialog = { + title: 'Error', + message: 'An error occured while toggling membership gifting settings. ' + + 'Please try again from the membership information panel in ' + + "YouTube's membership information interface.", + color: 'error' + }; + } break; case 'registerClientResponse': break; @@ -345,6 +355,7 @@ + { scrollToBottom(); diff --git a/src/components/ReportBanDialog.svelte b/src/components/ReportBanDialog.svelte index 1dab391a..2719fd5f 100644 --- a/src/components/ReportBanDialog.svelte +++ b/src/components/ReportBanDialog.svelte @@ -4,8 +4,7 @@ chatReportUserOptions } from '../ts/chat-constants'; import { - reportDialog, - alertDialog + reportDialog } from '../ts/storage'; import Dialog from './common/Dialog.svelte'; import type { Writable } from 'svelte/store'; @@ -30,15 +29,3 @@ }} color="error" disabled={!$optionStore}>Report
- - - {$alertDialog?.title} -
- {$alertDialog?.message} -
-
- -
-
From 36e7c5b609f4493060ed4f9e3e27cd1adec36af2 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 28 Jul 2022 15:33:16 -0700 Subject: [PATCH 11/16] extracting toggle --- src/components/GiftedMembershipToggle.svelte | 27 ++++++++++++++++++++ src/components/MembershipItem.svelte | 15 ++--------- src/scripts/chat-interceptor.ts | 6 ++--- src/ts/chat-actions.ts | 4 +-- src/ts/typings/chat.d.ts | 2 +- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/components/GiftedMembershipToggle.svelte b/src/components/GiftedMembershipToggle.svelte index e69de29b..ea3fa09a 100644 --- a/src/components/GiftedMembershipToggle.svelte +++ b/src/components/GiftedMembershipToggle.svelte @@ -0,0 +1,27 @@ + +
{ + if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, true); +}}> + + + + + + + Opt into membership gift reception for this channel + + +
diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index ab5136f9..c1189187 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -3,8 +3,8 @@ import MessageRun from './MessageRuns.svelte'; import { showProfileIcons, membershipGiftingEnabledOnChannel, port } from '../ts/storage'; import { membershipBackground, milestoneChatBackground } from '../ts/chat-constants'; - import { mdiInformation } from '@mdi/js'; import { fetchOrToggleMembershipGifting } from '../ts/chat-actions'; + import GiftedMembershipToggle from './GiftedMembershipToggle.svelte'; export let message: Ytc.ParsedMessage; @@ -87,18 +87,7 @@ alt={membershipGift.image.alt} title={membershipGift.image.alt} /> {#if optInPrompt && $membershipGiftingEnabledOnChannel === false} -
{ - if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, true); - }}> - - - - - - Opt into membership gift reception for this channel - - -
+ {/if} {/if}
diff --git a/src/scripts/chat-interceptor.ts b/src/scripts/chat-interceptor.ts index 26d084a7..2b902063 100644 --- a/src/scripts/chat-interceptor.ts +++ b/src/scripts/chat-interceptor.ts @@ -208,12 +208,12 @@ const chatLoaded = async (): Promise => { } const optRenderer = popup.onResponseReceivedActions[0].openPopupAction.popup.sponsorshipsGiftingOptInRenderer; currentValue = optRenderer.initialOptInStatus === 'SPONSORSHIPS_GIFTING_OPT_IN_STATUS_ENABLED'; - if (!msg.readonly) { + if (msg.newValue !== null) { const { optInCommand, optOutCommand }: { optInCommand: optCommand; optOutCommand: optCommand; } = optRenderer; - const toggle = currentValue ? optOutCommand : optInCommand; + const toggle = msg.newValue ? optOutCommand : optInCommand; const res = await (await fetcher(toggle.commandMetaData.webCommandMetadata.apiUrl, { ...heads, body: JSON.stringify({ @@ -226,7 +226,7 @@ const chatLoaded = async (): Promise => { }) })).json(); success = res.feedbackResponse[0].isProcessed as boolean; - if (success) currentValue = !currentValue; + if (success) currentValue = msg.newValue; } } catch (e) { console.debug('Error executing toggle membership gifting', e); diff --git a/src/ts/chat-actions.ts b/src/ts/chat-actions.ts index 1469d5d5..87c1cd70 100644 --- a/src/ts/chat-actions.ts +++ b/src/ts/chat-actions.ts @@ -32,11 +32,11 @@ export function useBanHammer( export function fetchOrToggleMembershipGifting( prompt: Ytc.OptInPrompt, port: Chat.Port | null, - toggle: boolean + newValue: boolean | null ): void { port?.postMessage({ type: 'toggleMembershipGifting', prompt, - readonly: !toggle + newValue }); } diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index e526b10b..9b1afb78 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -146,7 +146,7 @@ declare namespace Chat { interface toggleMembershipGiftingMsg { type: 'toggleMembershipGifting'; prompt: OptInPrompt; - readonly: boolean; + newValue: boolean | null; } interface toggleMembershipGiftingResponse { From 3805079d2fccf614a24d4b055ebd845b084a1367 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Thu, 28 Jul 2022 16:55:27 -0700 Subject: [PATCH 12/16] actually usable button --- src/components/GiftedMembershipToggle.svelte | 27 +++++--------------- src/components/Hyperchat.svelte | 16 +++++++----- src/components/MembershipItem.svelte | 13 +++++----- src/scripts/chat-interceptor.ts | 8 +++--- src/ts/storage.ts | 2 +- src/ts/typings/chat.d.ts | 1 + 6 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/components/GiftedMembershipToggle.svelte b/src/components/GiftedMembershipToggle.svelte index ea3fa09a..a1870437 100644 --- a/src/components/GiftedMembershipToggle.svelte +++ b/src/components/GiftedMembershipToggle.svelte @@ -1,27 +1,12 @@ -
{ - if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, true); -}}> - - - - - - - Opt into membership gift reception for this channel - +
+ { + if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, !$membershipGiftingStatus); + }}> + Opt into membership gift reception
diff --git a/src/components/Hyperchat.svelte b/src/components/Hyperchat.svelte index b0cb1c94..260b1c2c 100644 --- a/src/components/Hyperchat.svelte +++ b/src/components/Hyperchat.svelte @@ -42,7 +42,7 @@ lastOpenedVersion, selfChannelName, enableHighlightedMentions, - membershipGiftingEnabledOnChannel + membershipGiftingStatus } from '../ts/storage'; import { version } from '../manifest.json'; @@ -238,16 +238,18 @@ } break; case 'toggleMembershipGiftingResponse': - if (response.success) $membershipGiftingEnabledOnChannel = response.enabled; - else { + if ($membershipGiftingStatus !== null) { $alertDialog = { - title: 'Error', - message: 'An error occured while toggling membership gifting settings. ' + - 'Please try again from the membership information panel in ' + - "YouTube's membership information interface.", + title: response.success ? 'Success!' : 'Error', + message: (response.success + ? `Membership gift reception was successfully enabled for "${response.channelName}". You can opt out again through` + : `An error occured while toggling membership gifting settings for "${response.channelName}". + Please try again from ` + ) + "the membership information panel in YouTube's interface.", color: 'error' }; } + $membershipGiftingStatus = { enabled: response.enabled }; break; case 'registerClientResponse': break; diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index c1189187..33df70a5 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -1,7 +1,7 @@ @@ -86,7 +87,7 @@ src={membershipGift.image.src} alt={membershipGift.image.alt} title={membershipGift.image.alt} /> - {#if optInPrompt && $membershipGiftingEnabledOnChannel === false} + {#if optInPrompt && $membershipGiftingStatus?.enabled === false} {/if} {/if} diff --git a/src/scripts/chat-interceptor.ts b/src/scripts/chat-interceptor.ts index 2b902063..05ece4d6 100644 --- a/src/scripts/chat-interceptor.ts +++ b/src/scripts/chat-interceptor.ts @@ -182,6 +182,7 @@ const chatLoaded = async (): Promise => { }); } else if (msg.type === 'toggleMembershipGifting') { let currentValue = false; + let channelName = ''; try { const d = msg.prompt.buttonRenderer; if (d.isDisabled) return; @@ -208,12 +209,13 @@ const chatLoaded = async (): Promise => { } const optRenderer = popup.onResponseReceivedActions[0].openPopupAction.popup.sponsorshipsGiftingOptInRenderer; currentValue = optRenderer.initialOptInStatus === 'SPONSORSHIPS_GIFTING_OPT_IN_STATUS_ENABLED'; + channelName = optRenderer.subtitle.runs[1].text; if (msg.newValue !== null) { const { optInCommand, optOutCommand }: { optInCommand: optCommand; optOutCommand: optCommand; } = optRenderer; - const toggle = msg.newValue ? optOutCommand : optInCommand; + const toggle = msg.newValue ? optInCommand : optOutCommand; const res = await (await fetcher(toggle.commandMetaData.webCommandMetadata.apiUrl, { ...heads, body: JSON.stringify({ @@ -235,9 +237,9 @@ const chatLoaded = async (): Promise => { port.postMessage({ type: 'toggleMembershipGiftingResponse', success, - enabled: currentValue + enabled: currentValue, + channelName }); - console.log('toggleMembershipGiftingResponse', 'success:', success, 'enabled:', currentValue); } }); }); diff --git a/src/ts/storage.ts b/src/ts/storage.ts index 30d46c8c..ec338f54 100644 --- a/src/ts/storage.ts +++ b/src/ts/storage.ts @@ -79,4 +79,4 @@ export const currentProgress = writable(null as null | number); export const enableStickySuperchatBar = stores.addSyncStore('hc.enableStickySuperchatBar', true); export const enableHighlightedMentions = stores.addSyncStore('hc.enableHighlightedMentions', true); export const lastOpenedVersion = stores.addSyncStore('hc.lastOpenedVersion', ''); -export const membershipGiftingEnabledOnChannel = writable(null as null | boolean); +export const membershipGiftingStatus = writable(undefined as undefined | null | { enabled: boolean }); diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index 9b1afb78..be009b3e 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -153,6 +153,7 @@ declare namespace Chat { type: 'toggleMembershipGiftingResponse'; success: boolean; enabled: boolean; + channelName: string; } type BackgroundMessage = From 6cc05f6cbc8486ed35e3ea627554ce3b94b8b6ef Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Sat, 30 Jul 2022 19:53:35 -0700 Subject: [PATCH 13/16] v2.6.4 for beta --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index 6cd68378..3a4e2492 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -3,7 +3,7 @@ "name": "HyperChat by LiveTL", "homepage_url": "https://livetl.app/en/hyperchat/", "description": "YouTube chat, but it's fast and sleek!", - "version": "2.6.3", + "version": "2.6.4", "permissions": [ "storage" ], From ef27898b22914b2feeeee70b9e43d9b7146e0652 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Sat, 30 Jul 2022 21:17:03 -0700 Subject: [PATCH 14/16] i give up fck this im gonna just automate clicks --- src/components/GiftedMembershipToggle.svelte | 2 +- src/scripts/chat-interceptor.ts | 26 +++++++++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/components/GiftedMembershipToggle.svelte b/src/components/GiftedMembershipToggle.svelte index a1870437..03136758 100644 --- a/src/components/GiftedMembershipToggle.svelte +++ b/src/components/GiftedMembershipToggle.svelte @@ -5,7 +5,7 @@
{ - if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, !$membershipGiftingStatus); + if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, !($membershipGiftingStatus?.enabled)); }}> Opt into membership gift reception diff --git a/src/scripts/chat-interceptor.ts b/src/scripts/chat-interceptor.ts index 05ece4d6..d12c7283 100644 --- a/src/scripts/chat-interceptor.ts +++ b/src/scripts/chat-interceptor.ts @@ -186,18 +186,18 @@ const chatLoaded = async (): Promise => { try { const d = msg.prompt.buttonRenderer; if (d.isDisabled) return; - const { params, context } = parseServiceEndpoint(d.command, 'browseEndpoint'); - const popup = await (await fetch(`https://www.youtube.com/youtubei/v1/browse?key=${apiKey}&prettyPrint=false`, { + let { params, context } = parseServiceEndpoint(d.command, 'browseEndpoint'); + const popup = await fetcher(`https://www.youtube.com/youtubei/v1/browse?key=${apiKey}&prettyPrint=false`, { ...heads, body: JSON.stringify({ context, params, browseId: d.command.browseEndpoint.browseId }) - })).json(); + }); interface optCommand { clickTrackingParams: string; - commandMetaData: { + commandMetadata: { webCommandMetadata: { apiUrl: string; sendPost: boolean; @@ -215,8 +215,21 @@ const chatLoaded = async (): Promise => { optInCommand: optCommand; optOutCommand: optCommand; } = optRenderer; + console.log(optInCommand, optOutCommand); const toggle = msg.newValue ? optInCommand : optOutCommand; - const res = await (await fetcher(toggle.commandMetaData.webCommandMetadata.apiUrl, { + const pc = parseServiceEndpoint(toggle, 'feedbackEndpoint'); + params = pc.params; + context = pc.context; + context.request = { + useSsl: true, + consistencyTokenJars: [ + { + encryptedTokenJarContents: toggle.feedbackEndpoint.feedbackToken + } + ], + internalExperimentFlags: [] + }; + const res = (await fetcher(`https://www.youtube.com${toggle.commandMetadata.webCommandMetadata.apiUrl}`, { ...heads, body: JSON.stringify({ context, @@ -226,7 +239,8 @@ const chatLoaded = async (): Promise => { isFeedbackTokenUnencrypted: false, shouldMerge: false }) - })).json(); + })); + console.log(res); success = res.feedbackResponse[0].isProcessed as boolean; if (success) currentValue = msg.newValue; } From 9f192b4955f1d0c4d847d83ee5d83bc5e1bce561 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Sat, 30 Jul 2022 23:41:08 -0700 Subject: [PATCH 15/16] clickie wickie (not fully done yet) --- src/components/GiftedMembershipToggle.svelte | 21 +++-- src/components/Hyperchat.svelte | 14 +--- src/components/MembershipItem.svelte | 57 +++---------- src/scripts/chat-interceptor.ts | 88 +++++--------------- src/ts/chat-actions.ts | 10 +-- src/ts/chat-parser.ts | 3 +- src/ts/storage.ts | 1 - src/ts/typings/chat.d.ts | 4 - src/ts/typings/ytc.d.ts | 31 ------- 9 files changed, 52 insertions(+), 177 deletions(-) diff --git a/src/components/GiftedMembershipToggle.svelte b/src/components/GiftedMembershipToggle.svelte index 03136758..3561cd4e 100644 --- a/src/components/GiftedMembershipToggle.svelte +++ b/src/components/GiftedMembershipToggle.svelte @@ -1,12 +1,15 @@ -
- { - if (optInPrompt) fetchOrToggleMembershipGifting(optInPrompt, $port, !($membershipGiftingStatus?.enabled)); - }}> - Opt into membership gift reception - +
{ + toggleMembershipGifting($port); + }} + style="transform: translateX(3.5px);" + class="rounded-full flex justify-center items-center cursor-pointer w-8 h-8"> + + +
diff --git a/src/components/Hyperchat.svelte b/src/components/Hyperchat.svelte index 260b1c2c..c3da3536 100644 --- a/src/components/Hyperchat.svelte +++ b/src/components/Hyperchat.svelte @@ -41,8 +41,7 @@ enableStickySuperchatBar, lastOpenedVersion, selfChannelName, - enableHighlightedMentions, - membershipGiftingStatus + enableHighlightedMentions } from '../ts/storage'; import { version } from '../manifest.json'; @@ -238,18 +237,13 @@ } break; case 'toggleMembershipGiftingResponse': - if ($membershipGiftingStatus !== null) { + if (!response.success) { $alertDialog = { - title: response.success ? 'Success!' : 'Error', - message: (response.success - ? `Membership gift reception was successfully enabled for "${response.channelName}". You can opt out again through` - : `An error occured while toggling membership gifting settings for "${response.channelName}". - Please try again from ` - ) + "the membership information panel in YouTube's interface.", + title: 'Error', + message: "Please try again from YouTube's membership settings interface.", color: 'error' }; } - $membershipGiftingStatus = { enabled: response.enabled }; break; case 'registerClientResponse': break; diff --git a/src/components/MembershipItem.svelte b/src/components/MembershipItem.svelte index 33df70a5..dbaf7d7a 100644 --- a/src/components/MembershipItem.svelte +++ b/src/components/MembershipItem.svelte @@ -1,9 +1,8 @@ {#if membership || membershipGift}
{#if $showProfileIcons} @@ -82,14 +45,14 @@ {/if} {#if membershipGift} - {membershipGift.image.alt} - {#if optInPrompt && $membershipGiftingStatus?.enabled === false} - - {/if} +
+ {membershipGift.image.alt} + +
{/if}
{#if isMilestoneChat} diff --git a/src/scripts/chat-interceptor.ts b/src/scripts/chat-interceptor.ts index d12c7283..46fa22d7 100644 --- a/src/scripts/chat-interceptor.ts +++ b/src/scripts/chat-interceptor.ts @@ -181,78 +181,34 @@ const chatLoaded = async (): Promise => { success }); } else if (msg.type === 'toggleMembershipGifting') { - let currentValue = false; - let channelName = ''; + const clickItem = async (selector: string, condition = () => true): Promise => await new Promise((resolve, reject) => { + const interval = setInterval(() => { + const item = window.parent.document.querySelector(selector); + if (item != null) { + (item as HTMLButtonElement).click(); + if (condition()) { + clearInterval(interval); + resolve(); + } + } + }, 0); + setTimeout(() => { + clearInterval(interval); + reject(new Error(`Could not click ${selector}`)); + }, 500); + }); try { - const d = msg.prompt.buttonRenderer; - if (d.isDisabled) return; - let { params, context } = parseServiceEndpoint(d.command, 'browseEndpoint'); - const popup = await fetcher(`https://www.youtube.com/youtubei/v1/browse?key=${apiKey}&prettyPrint=false`, { - ...heads, - body: JSON.stringify({ - context, - params, - browseId: d.command.browseEndpoint.browseId - }) - }); - interface optCommand { - clickTrackingParams: string; - commandMetadata: { - webCommandMetadata: { - apiUrl: string; - sendPost: boolean; - }; - }; - feedbackEndpoint: { - feedbackToken: string; - }; - } - const optRenderer = popup.onResponseReceivedActions[0].openPopupAction.popup.sponsorshipsGiftingOptInRenderer; - currentValue = optRenderer.initialOptInStatus === 'SPONSORSHIPS_GIFTING_OPT_IN_STATUS_ENABLED'; - channelName = optRenderer.subtitle.runs[1].text; - if (msg.newValue !== null) { - const { optInCommand, optOutCommand }: { - optInCommand: optCommand; - optOutCommand: optCommand; - } = optRenderer; - console.log(optInCommand, optOutCommand); - const toggle = msg.newValue ? optInCommand : optOutCommand; - const pc = parseServiceEndpoint(toggle, 'feedbackEndpoint'); - params = pc.params; - context = pc.context; - context.request = { - useSsl: true, - consistencyTokenJars: [ - { - encryptedTokenJarContents: toggle.feedbackEndpoint.feedbackToken - } - ], - internalExperimentFlags: [] - }; - const res = (await fetcher(`https://www.youtube.com${toggle.commandMetadata.webCommandMetadata.apiUrl}`, { - ...heads, - body: JSON.stringify({ - context, - feedbackToken: { - 0: toggle.feedbackEndpoint.feedbackToken - }, - isFeedbackTokenUnencrypted: false, - shouldMerge: false - }) - })); - console.log(res); - success = res.feedbackResponse[0].isProcessed as boolean; - if (success) currentValue = msg.newValue; - } + await clickItem('div#owner .ytd-button-renderer .style-suggestive'); + const s = '#items > ytd-menu-service-item-renderer:nth-child(3) > tp-yt-paper-item > yt-formatted-string'; + await clickItem('.ytd-sponsorships-offer-renderer button', () => window.parent.document.querySelector(s) != null); + await clickItem(s); } catch (e) { - console.debug('Error executing toggle membership gifting', e); + console.debug('Error toggling membership gifting', e); success = false; } port.postMessage({ type: 'toggleMembershipGiftingResponse', - success, - enabled: currentValue, - channelName + success }); } }); diff --git a/src/ts/chat-actions.ts b/src/ts/chat-actions.ts index 87c1cd70..106dd3b4 100644 --- a/src/ts/chat-actions.ts +++ b/src/ts/chat-actions.ts @@ -29,14 +29,10 @@ export function useBanHammer( } } -export function fetchOrToggleMembershipGifting( - prompt: Ytc.OptInPrompt, - port: Chat.Port | null, - newValue: boolean | null +export function toggleMembershipGifting( + port: Chat.Port | null ): void { port?.postMessage({ - type: 'toggleMembershipGifting', - prompt, - newValue + type: 'toggleMembershipGifting' }); } diff --git a/src/ts/chat-parser.ts b/src/ts/chat-parser.ts index 40e0c951..2a1e6788 100644 --- a/src/ts/chat-parser.ts +++ b/src/ts/chat-parser.ts @@ -160,8 +160,7 @@ const parseAddChatItemAction = (action: Ytc.AddChatItemAction, isReplay = false, image: { src: fixUrl(header.image.thumbnails[0].url), alt: 'gift' - }, - optInPrompt: renderer.optInPrompt + } }; } else if (actionItem.liveChatSponsorshipsGiftRedemptionAnnouncementRenderer) { item.membershipGiftRedeem = true; diff --git a/src/ts/storage.ts b/src/ts/storage.ts index ec338f54..a09bd2bc 100644 --- a/src/ts/storage.ts +++ b/src/ts/storage.ts @@ -79,4 +79,3 @@ export const currentProgress = writable(null as null | number); export const enableStickySuperchatBar = stores.addSyncStore('hc.enableStickySuperchatBar', true); export const enableHighlightedMentions = stores.addSyncStore('hc.enableHighlightedMentions', true); export const lastOpenedVersion = stores.addSyncStore('hc.lastOpenedVersion', ''); -export const membershipGiftingStatus = writable(undefined as undefined | null | { enabled: boolean }); diff --git a/src/ts/typings/chat.d.ts b/src/ts/typings/chat.d.ts index be009b3e..f86cb3e7 100644 --- a/src/ts/typings/chat.d.ts +++ b/src/ts/typings/chat.d.ts @@ -145,15 +145,11 @@ declare namespace Chat { interface toggleMembershipGiftingMsg { type: 'toggleMembershipGifting'; - prompt: OptInPrompt; - newValue: boolean | null; } interface toggleMembershipGiftingResponse { type: 'toggleMembershipGiftingResponse'; success: boolean; - enabled: boolean; - channelName: string; } type BackgroundMessage = diff --git a/src/ts/typings/ytc.d.ts b/src/ts/typings/ytc.d.ts index 9a962949..0674c419 100644 --- a/src/ts/typings/ytc.d.ts +++ b/src/ts/typings/ytc.d.ts @@ -218,35 +218,6 @@ declare namespace Ytc { headerSubtext: SimpleTextObj | RunsObj; } - interface OptInPrompt { - buttonRenderer: { - style: string; - size: string; - isDisabled: boolean; - text: { - runs: Array<{text: string}>; - }; - icon: { - iconType: string; - }; - trackingParams: string; - command: { - clickTrackingParams: string; - commandMetadata: { - webCommandMetadata: { - sendPost: boolean; - apiUrl: string; - }; - }; - browseEndpoint: { - browseId: string; - params: string; - navigationType: string; - }; - }; - }; - } - interface MembershipGiftPurchaseRenderer extends IRenderer { header: { liveChatSponsorshipsHeaderRenderer: TextMessageRenderer & { @@ -254,7 +225,6 @@ declare namespace Ytc { image: Thumbnails; }; }; - optInPrompt?: OptInPrompt; } interface PlaceholderRenderer { // No idea what the purpose of this is @@ -354,7 +324,6 @@ declare namespace Ytc { interface ParsedMembershipGiftPurchase { headerPrimaryText: ParsedRun[]; image: ParsedImage; - optInPrompt?: OptInPrompt; } interface ParsedMessage { From 3595991c98b9ec1d7181a165b559dca6daeb0d83 Mon Sep 17 00:00:00 2001 From: Kento Nishi Date: Mon, 1 Aug 2022 14:38:26 -0700 Subject: [PATCH 16/16] add isYtFrame param --- src/components/GiftedMembershipToggle.svelte | 26 ++++++++++++-------- src/scripts/chat-injector.ts | 4 +++ src/ts/chat-constants.ts | 1 + 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/components/GiftedMembershipToggle.svelte b/src/components/GiftedMembershipToggle.svelte index 3561cd4e..a5d89ee4 100644 --- a/src/components/GiftedMembershipToggle.svelte +++ b/src/components/GiftedMembershipToggle.svelte @@ -2,14 +2,20 @@ import { port } from '../ts/storage'; import { toggleMembershipGifting } from '../ts/chat-actions'; import { mdiCogOutline } from '@mdi/js'; + import { + isYtFrame + } from '../ts/chat-constants'; -
{ - toggleMembershipGifting($port); - }} - style="transform: translateX(3.5px);" - class="rounded-full flex justify-center items-center cursor-pointer w-8 h-8"> - - - -
+ +{#if isYtFrame} +
{ + toggleMembershipGifting($port); + }} + style="transform: translateX(3.5px);" + class="rounded-full flex justify-center items-center cursor-pointer w-8 h-8"> + + + +
+{/if} diff --git a/src/scripts/chat-injector.ts b/src/scripts/chat-injector.ts index 0b370d6d..7bd6bb0f 100644 --- a/src/scripts/chat-injector.ts +++ b/src/scripts/chat-injector.ts @@ -46,6 +46,10 @@ const chatLoaded = async (): Promise => { const params = new URLSearchParams(); params.set('tabid', frameInfo.tabId.toString()); params.set('frameid', frameInfo.frameId.toString()); + try { + params.set('isYtFrame', window.parent.location.href !== window.location.href ? '1' : '0'); + } catch { + } if (frameIsReplay) params.set('isReplay', 'true'); const source = chrome.runtime.getURL( (isLiveTL ? 'hyperchat/index.html' : 'hyperchat.html') + diff --git a/src/ts/chat-constants.ts b/src/ts/chat-constants.ts index eeecfbca..383b3515 100644 --- a/src/ts/chat-constants.ts +++ b/src/ts/chat-constants.ts @@ -26,6 +26,7 @@ const params = new URLSearchParams(window.location.search); export const paramsTabId = params.get('tabid'); export const paramsFrameId = params.get('frameid'); export const paramsIsReplay = params.get('isReplay'); +export const isYtFrame = params.get('isYtFrame') === '1'; export const enum Theme { YOUTUBE = 'YOUTUBE',