11// ==UserScript==
2- // @name YT Music Autoclicker API (Proactive & Stealth Edition)
2+ // @name YT Music Autoclicker API (Hybrid Core Edition)
33// @namespace https://github.com/fgirolami29
4- // @version 3.1 .0
5- // @description Previene il popup di YT Music tramite reset API core e disabilita la telemetria/logging di YouTube .
4+ // @version 3.2 .0
5+ // @description Bypass proattivo via API core, sinkhole telemetrico e fallback reattivo a due stadi (Spacebar emulata + Notifica OS Focus). Zero errori TS .
66// @author Tu & Gemini
77// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACsAAAAgCAYAAACLmoEDAAABo0lEQVR4AdSXAZKDIBAEiR878zL1Zbmf5aY3txaWpqKyWCS1I4jotiMi6VLh75lSv1eFqdIKNks8qv7I9FR9JQE89mrr/KzNc5HXpOsuYgGrE0cd9eSD6n0mVauG5yKvCR7kWWdYNSoSnfxYCyU8g8C4kdcw0A6OtgD3jgHoF6x62I7KVsNe4k6umsWtUmZcA2P2W2DnYZDdQLPVHmd/AvB+A67x8RLAfuy0o8N0S0mRplTxFwVriKJlCqwGDGzoCwawpIh3GVhzJXoj2lFSxEFXg/WbF60PjeLhUR0WaICR6kXAl8AK0oMpDvn+ofISWD7pki89T7/Q1WEFyZgF9DSk218NFkhJEbdGBvb0GPI7zkvRsZzDyfBlJ7B5rtP1DBLQ4ke+BRIFi4vVIB08CvaQk578aAls0UR9NGFJf2BLzr/y3KnTZzB0NqhJ7842PxRk6miwVORIyw6bmQYr0CTgu0prVNlKYOBdbHyyl/9uaZQUCWhEZ3QFPHlc5AYS0Wb5Z2dt738jWlb5iM7opraV1J2ncVhb11IbeVzkniGVx+IPAAD///H503IAAAAGSURBVAMApvWIs8xfbPkAAAAASUVORK5CYII=
88// @include *://music.youtube.com/**
1313/* eslint-env browser, greasemonkey */
1414/* global globalThis */
1515
16- const VERSION = '3.1.0' ;
16+ const VERSION = '3.2.0' ;
17+ const ICON_BASE64 =
18+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACsAAAAgCAYAAACLmoEDAAABo0lEQVR4AdSXAZKDIBAEiR878zL1Zbmf5aY3txaWpqKyWCS1I4jotiMi6VLh75lSv1eFqdIKNks8qv7I9FR9JQE89mrr/KzNc5HXpOsuYgGrE0cd9eSD6n0mVauG5yKvCR7kWWdYNSoSnfxYCyU8g8C4kdcw0A6OtgD3jgHoF6x62I7KVsNe4k6umsWtUmZcA2P2W2DnYZDdQLPVHmd/AvB+A67x8RLAfuy0o8N0S0mRplTxFwVriKJlCqwGDGzoCwawpIh3GVhzJXoj2lFSxEFXg/WbF60PjeLhUR0WaICR6kXAl8AK0oMpDvn+ofISWD7pki89T7/Q1WEFyZgF9DSk218NFkhJEbdGBvb0GPI7zkvRsZzDyfBlJ7B5rtP1DBLQ4ke+BRIFi4vVIB08CvaQk578aAls0UR9NGFJf2BLzr/y3KnTZzB0NqhJ7842PxRk6miwVORIyw6bmQYr0CTgu0prVNlKYOBdbHyyl/9uaZQUCWhEZ3QFPHlc5AYS0Wb5Z2dt738jWlb5iM7opraV1J2ncVhb11IbeVzkniGVx+IPAAD///H503IAAAAGSURBVAMApvWIs8xfbPkAAAAASUVORK5CYII=' ;
1719
1820/**
1921 * @typedef {Object } YTBYP_Config
2022 * @property {number } pingIntervalMs - Intervallo di reset del timer AFK (default: 5 min).
2123 * @property {boolean } blockTelemetry - Flag per abilitare/disabilitare il sinkhole network.
2224 * @property {boolean } debugMode - Flag per abilitare/disabilitare l'output verboso.
25+ * @property {number } fallbackTimeoutMs - Timeout prima di innescare la notifica OS di emergenza (ms).
2326 */
2427
2528/** @type {YTBYP_Config } */
2629const CONFIG = {
2730 pingIntervalMs : 5 * 60 * 1000 ,
2831 blockTelemetry : true ,
29- debugMode : true
32+ debugMode : true ,
33+ fallbackTimeoutMs : 2000 ,
3034} ;
3135
3236( function ( window , globalThis ) {
@@ -36,6 +40,10 @@ const CONFIG = {
3640 const _global = globalThis ;
3741 const targetWindow = typeof _global . unsafeWindow !== 'undefined' ? _global . unsafeWindow : window ;
3842
43+ /**
44+ * Dispatcher standardizzato per output di log diagnostici.
45+ * @param {string } message
46+ */
3947 const debug = ( message ) => {
4048 if ( ! CONFIG . debugMode ) return ;
4149 const prefix = '[YT Music Bypass] ' ;
@@ -46,12 +54,16 @@ const CONFIG = {
4654 }
4755 } ;
4856
57+ /**
58+ * API Pubblica esportata nel window per debug e controllo runtime.
59+ */
4960 const API = {
5061 config : CONFIG ,
5162 interval : undefined ,
63+ modalObserver : undefined ,
5264 start : ( ) => { } ,
5365 stop : ( ) => { } ,
54- forcePing : ( ) => { }
66+ forcePing : ( ) => { } ,
5567 } ;
5668
5769 /**
@@ -63,25 +75,25 @@ const CONFIG = {
6375
6476 // 1. Intercettazione Fetch API
6577 const originalFetch = window . fetch ;
66- window . fetch = async function ( ...args ) {
78+ window . fetch = async function ( ...args ) {
6779 const url = typeof args [ 0 ] === 'string' ? args [ 0 ] : args [ 0 ] ?. url || '' ;
6880 const blockList = [ '/youtubei/v1/log_event' , '/api/stats/qoe' , '/api/stats/playback' , 'ptracking' , 'play_tracking' ] ;
6981
70- if ( blockList . some ( endpoint => url . includes ( endpoint ) ) ) {
82+ if ( blockList . some ( ( endpoint ) => url . includes ( endpoint ) ) ) {
7183 debug ( `[Sinkhole] Fetch droppata: ${ url . split ( '?' ) [ 0 ] } ` ) ;
7284 return new Response ( JSON . stringify ( { } ) , { status : 200 , statusText : 'OK' } ) ;
7385 }
7486 return originalFetch . apply ( this , args ) ;
7587 } ;
7688
77- // 2. Intercettazione XHR
89+ // 2. Intercettazione XHR (Trasporto Legacy)
7890 const originalXhrOpen = XMLHttpRequest . prototype . open ;
79- XMLHttpRequest . prototype . open = function ( method , url , ...rest ) {
91+ XMLHttpRequest . prototype . open = function ( method , url , ...rest ) {
8092 const blockList = [ '/log_event' , '/stats/' , 'ptracking' ] ;
81-
82- if ( typeof url === 'string' && blockList . some ( endpoint => url . includes ( endpoint ) ) ) {
93+
94+ if ( typeof url === 'string' && blockList . some ( ( endpoint ) => url . includes ( endpoint ) ) ) {
8395 debug ( `[Sinkhole] XHR droppata: ${ url . split ( '?' ) [ 0 ] } ` ) ;
84- this . send = function ( ) {
96+ this . send = function ( ) {
8597 Object . defineProperty ( this , 'readyState' , { value : 4 } ) ;
8698 Object . defineProperty ( this , 'status' , { value : 200 } ) ;
8799 Object . defineProperty ( this , 'responseText' , { value : '{}' } ) ;
@@ -96,12 +108,20 @@ const CONFIG = {
96108 // 3. Stubbing Metodi Nativi Core Player
97109 const overrideCoreLogging = ( ) => {
98110 const playerEl = document . querySelector ( 'ytmusic-player' ) ;
99- const corePlayer = playerEl ? /** @type {any } */ ( playerEl ) . getPlayer ?. ( ) : null ;
111+ const corePlayer = playerEl ? /** @type {any } */ ( playerEl ) . getPlayer ?. ( ) : null ;
100112 if ( ! corePlayer ) return ;
101113
102- const logMethods = [ 'logImaAdEvent' , 'logApiCall' , 'logClick' , 'logVisibility' , 'sendImpression' , 'impressionLog' , 'setTrackingParams' ] ;
114+ const logMethods = [
115+ 'logImaAdEvent' ,
116+ 'logApiCall' ,
117+ 'logClick' ,
118+ 'logVisibility' ,
119+ 'sendImpression' ,
120+ 'impressionLog' ,
121+ 'setTrackingParams' ,
122+ ] ;
103123 let patched = 0 ;
104- logMethods . forEach ( method => {
124+ logMethods . forEach ( ( method ) => {
105125 if ( typeof corePlayer [ method ] === 'function' ) {
106126 corePlayer [ method ] = ( ) => { } ;
107127 patched ++ ;
@@ -113,45 +133,147 @@ const CONFIG = {
113133 setTimeout ( overrideCoreLogging , 5000 ) ;
114134 } ;
115135
136+ /**
137+ * Esegue il ping alle API interne per resettare i contatori di attività locale e server-side.
138+ */
116139 const executeEngagementPing = ( ) => {
117140 const playerEl = document . querySelector ( 'ytmusic-player' ) ;
118- const corePlayer = playerEl ? /** @type {any } */ ( playerEl ) . getPlayer ?. ( ) : null ;
141+ const corePlayer = playerEl ? /** @type {any } */ ( playerEl ) . getPlayer ?. ( ) : null ;
119142
120143 if ( corePlayer ) {
121144 let successCount = 0 ;
122- if ( typeof corePlayer . updateLastActiveTime === 'function' ) { corePlayer . updateLastActiveTime ( ) ; successCount ++ ; }
123- if ( typeof corePlayer . setUserEngagement === 'function' ) { corePlayer . setUserEngagement ( ) ; successCount ++ ; }
145+ if ( typeof corePlayer . updateLastActiveTime === 'function' ) {
146+ corePlayer . updateLastActiveTime ( ) ;
147+ successCount ++ ;
148+ }
149+ if ( typeof corePlayer . setUserEngagement === 'function' ) {
150+ corePlayer . setUserEngagement ( ) ;
151+ successCount ++ ;
152+ }
124153 if ( successCount > 0 ) debug ( '✅ Ping inviato. Timer inattività (AFK) azzerato.' ) ;
125154 }
126155 } ;
127156
157+ /**
158+ * Valuta l'effettiva visibilità di un nodo nel DOM.
159+ * @param {Element | null } el
160+ */
161+ const isVisible = ( el ) => {
162+ if ( ! el ) return false ;
163+ const style = window . getComputedStyle ( el ) ;
164+ return (
165+ style . display !== 'none' &&
166+ style . visibility !== 'hidden' &&
167+ style . opacity !== '0' &&
168+ /** @type {HTMLElement } */ ( el ) . offsetWidth > 0
169+ ) ;
170+ } ;
171+
172+ /**
173+ * Esegue il dispatcher di fallback reattivo monitorando il DOM per instanziazioni anomale del modal.
174+ */
175+ const initReactiveFallback = ( ) => {
176+ debug ( 'Avvio strato di fallback reattivo (Observer su Body)...' ) ;
177+
178+ API . modalObserver = new MutationObserver ( ( mutations ) => {
179+ for ( const mutation of mutations ) {
180+ for ( const node of mutation . addedNodes ) {
181+ if ( node . nodeType === 1 ) {
182+ const el = /** @type {HTMLElement } */ ( node ) ;
183+ const modal = el . tagName === 'YTMUSIC-YOU-THERE-RENDERER' ? el : el . querySelector ( 'ytmusic-you-there-renderer' ) ;
184+
185+ if ( modal ) {
186+ debug ( '🚨 Fallback Innescato: Rilevato modal AFK nonostante il ping proattivo.' ) ;
187+
188+ // Livello Fallback 1: Simulazione nativa Spacebar
189+ const spaceEvent = new KeyboardEvent ( 'keydown' , {
190+ key : ' ' ,
191+ code : 'Space' ,
192+ keyCode : 32 ,
193+ bubbles : true ,
194+ cancelable : true ,
195+ } ) ;
196+ document . body . dispatchEvent ( spaceEvent ) ;
197+ debug ( 'Tasto Spazio emulato e dislocato sul body.' ) ;
198+
199+ // Livello Fallback 2: Notifica OS interattiva (Extrema Ratio)
200+ setTimeout ( ( ) => {
201+ if ( isVisible ( modal ) && 'Notification' in window ) {
202+ debug ( '⚠️ Spacebar fallita o ignorata. Lancio notifica OS di emergenza.' ) ;
203+
204+ const spawnNotification = ( ) => {
205+ const n = new Notification ( 'YT Music Bloccato ⏸️' , {
206+ body : 'Il blocco AFK ha eluso i controlli. Clicca per forzare il focus e riprendere.' ,
207+ icon : ICON_BASE64 ,
208+ requireInteraction : true ,
209+ } ) ;
210+
211+ n . onclick = ( ) => {
212+ window . focus ( ) ;
213+ n . close ( ) ;
214+
215+ // Esecuzione forza bruta sul nodo bottone come clean-up finale
216+ const yesBtn =
217+ document . querySelector ( 'yt-button-renderer[dialog-confirm] button' ) ||
218+ modal . querySelector ( 'button' ) ;
219+ if ( yesBtn ) {
220+ /** @type {HTMLElement } */ ( yesBtn ) . click ( ) ;
221+ debug ( 'Focus completato. Click fisico sul DOM eseguito.' ) ;
222+ }
223+ } ;
224+ } ;
225+
226+ if ( Notification . permission === 'granted' ) {
227+ spawnNotification ( ) ;
228+ } else if ( Notification . permission !== 'denied' ) {
229+ Notification . requestPermission ( ) . then ( ( p ) => p === 'granted' && spawnNotification ( ) ) ;
230+ }
231+ } else {
232+ debug ( '✅ Modal risolto dalla spacebar. Nessun intervento OS richiesto.' ) ;
233+ }
234+ } , CONFIG . fallbackTimeoutMs ) ;
235+ }
236+ }
237+ }
238+ }
239+ } ) ;
240+
241+ API . modalObserver . observe ( document . body , { childList : true , subtree : true } ) ;
242+ } ;
243+
128244 let isRunning = false ;
129245
130246 API . start = ( ) => {
131247 if ( isRunning ) return ;
132248 isRunning = true ;
133-
134- debug ( `Inizializzazione v${ VERSION } .` ) ;
249+
250+ debug ( `Inizializzazione v${ VERSION } [Hybrid Core Architecture].` ) ;
251+
252+ // Setup Componenti
135253 initTelemetryBlocker ( ) ;
254+ initReactiveFallback ( ) ;
136255
256+ // Innesco Routine
137257 setTimeout ( executeEngagementPing , 10000 ) ;
138258 API . interval = window . setInterval ( executeEngagementPing , CONFIG . pingIntervalMs ) ;
139259 } ;
140260
141261 API . stop = ( ) => {
142262 isRunning = false ;
143263 if ( API . interval !== undefined ) clearInterval ( API . interval ) ;
144- debug ( 'Demone arrestato. Protezione AFK disattivata.' ) ;
264+ if ( API . modalObserver ) API . modalObserver . disconnect ( ) ;
265+ debug ( 'Demone arrestato. Protezione disattivata.' ) ;
145266 } ;
146267
147268 API . forcePing = executeEngagementPing ;
148269
270+ // Binding Global
149271 targetWindow . ytbyp = API ;
150-
272+
273+ // Auto-avvio sicuro
151274 if ( document . readyState === 'complete' || document . readyState === 'interactive' ) {
152275 API . start ( ) ;
153276 } else {
154277 document . addEventListener ( 'DOMContentLoaded' , API . start ) ;
155278 }
156-
157- } ) ( window , globalThis ) ;
279+ } ) ( window , globalThis ) ;
0 commit comments