11import type { Unsubscriber } from './queue' ;
22import { ytcQueue } from './queue' ;
33import sha1 from 'sha-1' ;
4- import { chatReportUserOptions , ChatUserActions , ChatReportUserOptions } from '../ts/chat-constants' ;
4+ import { chatReportUserOptions , ChatUserActions , ChatReportUserOptions , ChatPollActions } from '../ts/chat-constants' ;
55
66const currentDomain = location . protocol . includes ( 'youtube' ) ? ( location . protocol + '//' + location . host ) : 'https://www.youtube.com' ;
77
@@ -179,6 +179,38 @@ const sendLtlMessage = (message: Chat.LtlMessage): void => {
179179 ) ;
180180} ;
181181
182+ function getCookie ( name : string ) : string {
183+ const value = `; ${ document . cookie } ` ;
184+ const parts = value . split ( `; ${ name } =` ) ;
185+ if ( parts . length === 2 ) return ( parts . pop ( ) ?? '' ) . split ( ';' ) . shift ( ) ?? '' ;
186+ return '' ;
187+ }
188+
189+ function parseServiceEndpoint ( baseContext : any , serviceEndpoint : any , prop : string ) : { params : string , context : any } {
190+ const { clickTrackingParams, [ prop ] : { params } } = serviceEndpoint ;
191+ const clonedContext = JSON . parse ( JSON . stringify ( baseContext ) ) ;
192+ clonedContext . clickTracking = {
193+ clickTrackingParams
194+ } ;
195+ return {
196+ params,
197+ context : clonedContext
198+ } ;
199+ }
200+
201+ const fetcher = async ( ...args : any [ ] ) : Promise < any > => {
202+ return await new Promise ( ( resolve ) => {
203+ const encoded = JSON . stringify ( args ) ;
204+ window . addEventListener ( 'proxyFetchResponse' , ( e ) => {
205+ const response = JSON . parse ( ( e as CustomEvent ) . detail ) ;
206+ resolve ( response ) ;
207+ } ) ;
208+ window . dispatchEvent ( new CustomEvent ( 'proxyFetchRequest' , {
209+ detail : encoded
210+ } ) ) ;
211+ } ) ;
212+ } ;
213+
182214const executeChatAction = async (
183215 message : Ytc . ParsedMessage ,
184216 ytcfg : YtCfg ,
@@ -187,31 +219,13 @@ const executeChatAction = async (
187219) : Promise < void > => {
188220 if ( message . params == null ) return ;
189221
190- const fetcher = async ( ...args : any [ ] ) : Promise < any > => {
191- return await new Promise ( ( resolve ) => {
192- const encoded = JSON . stringify ( args ) ;
193- window . addEventListener ( 'proxyFetchResponse' , ( e ) => {
194- const response = JSON . parse ( ( e as CustomEvent ) . detail ) ;
195- resolve ( response ) ;
196- } ) ;
197- window . dispatchEvent ( new CustomEvent ( 'proxyFetchRequest' , {
198- detail : encoded
199- } ) ) ;
200- } ) ;
201- } ;
202-
203222 let success = true ;
204223 try {
205224 const apiKey = ytcfg . data_ . INNERTUBE_API_KEY ;
206225 const contextMenuUrl = `${ currentDomain } /youtubei/v1/live_chat/get_item_context_menu?params=` +
207226 `${ encodeURIComponent ( message . params ) } &pbj=1&key=${ apiKey } &prettyPrint=false` ;
208227 const baseContext = ytcfg . data_ . INNERTUBE_CONTEXT ;
209- function getCookie ( name : string ) : string {
210- const value = `; ${ document . cookie } ` ;
211- const parts = value . split ( `; ${ name } =` ) ;
212- if ( parts . length === 2 ) return ( parts . pop ( ) ?? '' ) . split ( ';' ) . shift ( ) ?? '' ;
213- return '' ;
214- }
228+
215229 const time = Math . floor ( Date . now ( ) / 1000 ) ;
216230 const SAPISID = getCookie ( '__Secure-3PAPISID' ) ;
217231 const sha = sha1 ( `${ time } ${ SAPISID } ${ currentDomain } ` ) ;
@@ -228,19 +242,9 @@ const executeChatAction = async (
228242 ...heads ,
229243 body : JSON . stringify ( { context : baseContext } )
230244 } ) ;
231- function parseServiceEndpoint ( serviceEndpoint : any , prop : string ) : { params : string , context : any } {
232- const { clickTrackingParams, [ prop ] : { params } } = serviceEndpoint ;
233- const clonedContext = JSON . parse ( JSON . stringify ( baseContext ) ) ;
234- clonedContext . clickTracking = {
235- clickTrackingParams
236- } ;
237- return {
238- params,
239- context : clonedContext
240- } ;
241- }
245+
242246 if ( action === ChatUserActions . BLOCK ) {
243- const { params, context } = parseServiceEndpoint (
247+ const { params, context } = parseServiceEndpoint ( baseContext ,
244248 res . liveChatItemContextMenuSupportedRenderers . menuRenderer . items [ 1 ]
245249 . menuNavigationItemRenderer . navigationEndpoint . confirmDialogEndpoint
246250 . content . confirmDialogRenderer . confirmButton . buttonRenderer . serviceEndpoint ,
@@ -254,7 +258,7 @@ const executeChatAction = async (
254258 } )
255259 } ) ;
256260 } else if ( action === ChatUserActions . REPORT_USER ) {
257- const { params, context } = parseServiceEndpoint (
261+ const { params, context } = parseServiceEndpoint ( baseContext ,
258262 res . liveChatItemContextMenuSupportedRenderers . menuRenderer . items [ 0 ] . menuServiceItemRenderer . serviceEndpoint ,
259263 'getReportFormEndpoint'
260264 ) ;
@@ -296,6 +300,46 @@ const executeChatAction = async (
296300 ) ;
297301} ;
298302
303+ const executePollAction = async (
304+ poll : Ytc . ParsedPoll ,
305+ ytcfg : YtCfg ,
306+ action : ChatPollActions ,
307+ ) : Promise < void > => {
308+ try {
309+ const apiKey = ytcfg . data_ . INNERTUBE_API_KEY ;
310+ const baseContext = ytcfg . data_ . INNERTUBE_CONTEXT ;
311+
312+ const time = Math . floor ( Date . now ( ) / 1000 ) ;
313+ const SAPISID = getCookie ( '__Secure-3PAPISID' ) ;
314+ const sha = sha1 ( `${ time } ${ SAPISID } ${ currentDomain } ` ) ;
315+ const auth = `SAPISIDHASH ${ time } _${ sha } ` ;
316+ const heads = {
317+ headers : {
318+ 'Content-Type' : 'application/json' ,
319+ Accept : '*/*' ,
320+ Authorization : auth
321+ } ,
322+ method : 'POST'
323+ } ;
324+
325+ if ( action === ChatPollActions . END_POLL ) {
326+ const params = poll . item . action ?. params || '' ;
327+ const url = poll . item . action ?. api || '/youtubei/v1/live_chat/live_chat_action' ;
328+
329+ // Call YouTube API to end the poll
330+ await fetcher ( `${ currentDomain } ${ url } ?key=${ apiKey } &prettyPrint=false` , {
331+ ...heads ,
332+ body : JSON . stringify ( {
333+ params,
334+ context : baseContext
335+ } )
336+ } ) ;
337+ }
338+ } catch ( e ) {
339+ console . debug ( 'Error executing poll action' , e ) ;
340+ }
341+ }
342+
299343export const initInterceptor = (
300344 source : Chat . InterceptorSource ,
301345 ytcfg : YtCfg ,
@@ -335,6 +379,9 @@ export const initInterceptor = (
335379 case 'executeChatAction' :
336380 executeChatAction ( message . message , ytcfg , message . action , message . reportOption ) . catch ( console . error ) ;
337381 break ;
382+ case 'executePollAction' :
383+ executePollAction ( message . poll , ytcfg , message . action ) . catch ( console . error ) ;
384+ break ;
338385 case 'ping' :
339386 port . postMessage ( { type : 'ping' } ) ;
340387 break ;
0 commit comments