Duolingo Ad Remover with Plus Features

Removes ads from Duolingo and enables Plus subscription features

Size

16.8 KB

Version

1.1.1

Created

Nov 4, 2025

Updated

about 1 month ago

1// ==UserScript==
2// @name		Duolingo Ad Remover with Plus Features
3// @description		Removes ads from Duolingo and enables Plus subscription features
4// @version		1.1.1
5// @match		https://*.duolingo.com/*
6// @icon		https://d35aaqx5ub95lt.cloudfront.net/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Duolingo Ad Remover with Plus Features - Starting...');
12
13    // Add custom styles to hide ads and premium prompts
14    TM_addStyle(`
15        /* Hide all ads */
16        .adsbygoogle,
17        ins.adsbygoogle,
18        [class*="ad-container"],
19        [class*="advertisement"],
20        ._3D_HB {
21            display: none !important;
22            visibility: hidden !important;
23            height: 0 !important;
24            width: 0 !important;
25            overflow: hidden !important;
26        }
27
28        /* Hide premium/super subscription banners */
29        [data-test="plus-offer-logo"],
30        ._16rRh._2cnFr._1T_BQ,
31        [class*="plus-offer"],
32        [class*="premium-banner"],
33        div:has(> img[data-test="plus-offer-logo"]) {
34            display: none !important;
35        }
36
37        /* Hide "Get Super" buttons and prompts */
38        button:has(span:contains("Obtén Súper")),
39        button:has(span:contains("Get Super")),
40        [class*="super-upsell"],
41        [class*="premium-upsell"] {
42            display: none !important;
43        }
44
45        /* Hide "Hide ads" text/buttons */
46        ._1MA1B._1WLlV._1KSsU._2Rt1l {
47            display: none !important;
48        }
49    `);
50
51    // Function to remove ads and premium prompts
52    function removeAdsAndPrompts() {
53        console.log('Removing ads and premium prompts...');
54
55        // Remove Google AdSense ads
56        const ads = document.querySelectorAll('.adsbygoogle, ins.adsbygoogle, ._3D_HB');
57        ads.forEach(ad => {
58            console.log('Removing ad element:', ad);
59            ad.remove();
60        });
61
62        // Remove premium/super subscription banners
63        const premiumBanners = document.querySelectorAll('[data-test="plus-offer-logo"]');
64        premiumBanners.forEach(banner => {
65            console.log('Removing premium banner:', banner);
66            // Remove the parent container
67            const container = banner.closest('._16rRh, ._2cnFr, div[class*="premium"]');
68            if (container) {
69                container.remove();
70            }
71        });
72
73        // Remove "Get Super" buttons
74        const superButtons = document.querySelectorAll('button');
75        superButtons.forEach(button => {
76            const buttonText = button.textContent.toLowerCase();
77            if (buttonText.includes('obtén súper') || 
78                buttonText.includes('get super') || 
79                buttonText.includes('upgrade') ||
80                buttonText.includes('try super')) {
81                console.log('Removing super button:', button);
82                const container = button.closest('._16rRh, ._2cnFr, div[class*="upsell"]');
83                if (container) {
84                    container.remove();
85                } else {
86                    button.remove();
87                }
88            }
89        });
90
91        // Remove "Hide ads" text
92        const hideAdsText = document.querySelectorAll('._1MA1B._1WLlV._1KSsU._2Rt1l');
93        hideAdsText.forEach(text => {
94            console.log('Removing hide ads text:', text);
95            text.remove();
96        });
97
98        // Remove any remaining ad containers
99        const adContainers = document.querySelectorAll('[class*="ad-container"], [class*="advertisement"]');
100        adContainers.forEach(container => {
101            console.log('Removing ad container:', container);
102            container.remove();
103        });
104    }
105
106    // Function to unlock premium features
107    function unlockPremiumFeatures() {
108        console.log('Attempting to unlock premium features...');
109
110        // Try to modify user data if it exists in the window object
111        if (window.duo && window.duo.user) {
112            console.log('Found duo.user object, attempting to enable premium...');
113            window.duo.user.hasPlus = true;
114            window.duo.user.isPremium = true;
115            window.duo.user.plusStatus = 'ACTIVE';
116            window.duo.user.subscription = { status: 'active', itemName: 'plus_subscription' };
117            window.duo.user.lingots = 999999; // Unlimited gems
118            window.duo.user.rupees = 999999; // Unlimited lingots
119            window.duo.user.unlimitedHeartsAvailable = true;
120            window.duo.user.hasUnlimitedHearts = true;
121            window.duo.user.hearts = 999999;
122            window.duo.user.maxHearts = 999999;
123            window.duo.user.canAccessLegendary = true;
124            window.duo.user.hasUnlimitedLegendary = true;
125            window.duo.user.adFree = true;
126            window.duo.user.monthlyStreak = { isEnabled: true };
127            window.duo.user.practiceRemindersSettings = { isEnabled: true };
128        }
129
130        // Check for other common premium status variables
131        if (window.duoState) {
132            console.log('Found duoState object, attempting to enable premium...');
133            if (window.duoState.user) {
134                window.duoState.user.hasPlus = true;
135                window.duoState.user.isPremium = true;
136                window.duoState.user.plusStatus = 'ACTIVE';
137                window.duoState.user.lingots = 999999;
138                window.duoState.user.rupees = 999999;
139                window.duoState.user.unlimitedHeartsAvailable = true;
140                window.duoState.user.hasUnlimitedHearts = true;
141                window.duoState.user.hearts = 999999;
142                window.duoState.user.maxHearts = 999999;
143                window.duoState.user.canAccessLegendary = true;
144                window.duoState.user.hasUnlimitedLegendary = true;
145                window.duoState.user.adFree = true;
146            }
147        }
148
149        // Intercept localStorage to modify premium status
150        try {
151            const originalSetItem = localStorage.setItem;
152            localStorage.setItem = function(key, value) {
153                if (key.includes('user') || key.includes('premium') || key.includes('plus')) {
154                    try {
155                        const data = JSON.parse(value);
156                        if (data && typeof data === 'object') {
157                            data.hasPlus = true;
158                            data.isPremium = true;
159                            data.unlimitedHeartsAvailable = true;
160                            data.hasUnlimitedHearts = true;
161                            data.adFree = true;
162                            value = JSON.stringify(data);
163                        }
164                    } catch (e) {}
165                }
166                return originalSetItem.call(this, key, value);
167            };
168            console.log('LocalStorage interceptor installed');
169        } catch (e) {
170            console.log('Could not install localStorage interceptor:', e);
171        }
172
173        // Set unlimited hearts/lives
174        setUnlimitedHearts();
175        
176        // Unlock legendary levels
177        unlockLegendaryLevels();
178        
179        // Unlock practice hub
180        unlockPracticeHub();
181        
182        // Restore old profile picture system
183        restoreOldProfilePictures();
184    }
185
186    // Function to set unlimited hearts
187    function setUnlimitedHearts() {
188        console.log('Setting unlimited hearts...');
189        
190        // Find hearts counter and set to infinity
191        const heartsCounters = document.querySelectorAll('[data-test="hearts-menu"] span.xb7v_, [data-test="hearts-menu"] span._1ceMn, [data-test="heart-count"]');
192        heartsCounters.forEach(counter => {
193            console.log('Setting heart counter to unlimited:', counter);
194            counter.textContent = '∞';
195            counter.style.fontSize = '20px';
196        });
197
198        // Try to modify hearts in window objects
199        if (window.duo && window.duo.user) {
200            window.duo.user.hearts = 999999;
201            window.duo.user.maxHearts = 999999;
202            console.log('Set hearts in duo.user to unlimited');
203        }
204
205        if (window.duoState && window.duoState.user) {
206            window.duoState.user.hearts = 999999;
207            window.duoState.user.maxHearts = 999999;
208            console.log('Set hearts in duoState.user to unlimited');
209        }
210
211        // Intercept and modify hearts API responses
212        const originalFetch = window.fetch;
213        window.fetch = function(...args) {
214            return originalFetch.apply(this, args).then(response => {
215                const clonedResponse = response.clone();
216                clonedResponse.json().then(data => {
217                    if (data && typeof data === 'object') {
218                        if (data.hearts !== undefined) {
219                            data.hearts = 999999;
220                        }
221                        if (data.maxHearts !== undefined) {
222                            data.maxHearts = 999999;
223                        }
224                    }
225                }).catch(() => {});
226                return response;
227            });
228        };
229    }
230
231    // Function to unlock legendary levels
232    function unlockLegendaryLevels() {
233        console.log('Unlocking legendary levels...');
234
235        // Find all legendary level buttons and unlock them
236        const legendaryButtons = document.querySelectorAll('[aria-label*="Nivel legendario"], [aria-label*="legendary level"], [data-test*="legendary"]');
237        legendaryButtons.forEach(button => {
238            console.log('Unlocking legendary button:', button);
239            
240            // Remove any lock icons or disabled states
241            button.classList.remove('locked', 'disabled', '_2EY2C');
242            button.removeAttribute('disabled');
243            
244            // Make it clickable
245            button.style.pointerEvents = 'auto';
246            button.style.opacity = '1';
247            button.style.cursor = 'pointer';
248            
249            // Remove any overlay locks
250            const lockOverlay = button.querySelector('[class*="lock"], [class*="premium"]');
251            if (lockOverlay) {
252                lockOverlay.remove();
253            }
254        });
255
256        // Remove legendary level restrictions from window objects
257        if (window.duo && window.duo.user) {
258            window.duo.user.canAccessLegendary = true;
259            window.duo.user.hasUnlimitedLegendary = true;
260            console.log('Enabled legendary access in duo.user');
261        }
262
263        if (window.duoState && window.duoState.user) {
264            window.duoState.user.canAccessLegendary = true;
265            window.duoState.user.hasUnlimitedLegendary = true;
266            console.log('Enabled legendary access in duoState.user');
267        }
268
269        // Style legendary buttons to look unlocked
270        TM_addStyle(`
271            /* Unlock legendary levels */
272            [aria-label*="Nivel legendario"],
273            [aria-label*="legendary level"],
274            [data-test*="legendary"] {
275                pointer-events: auto !important;
276                opacity: 1 !important;
277                cursor: pointer !important;
278            }
279            
280            /* Remove lock icons from legendary */
281            [aria-label*="Nivel legendario"] [class*="lock"],
282            [aria-label*="legendary level"] [class*="lock"] {
283                display: none !important;
284            }
285            
286            /* Make legendary buttons look active */
287            .HPdUG._2EY2C {
288                opacity: 1 !important;
289                filter: none !important;
290            }
291        `);
292    }
293
294    // Function to unlock practice hub (free practice)
295    function unlockPracticeHub() {
296        console.log('Unlocking practice hub...');
297
298        // Find practice hub link and ensure it's accessible
299        const practiceHubLink = document.querySelector('[data-test="practice-hub-nav"]');
300        if (practiceHubLink) {
301            console.log('Found practice hub link, ensuring it is unlocked');
302            practiceHubLink.style.pointerEvents = 'auto';
303            practiceHubLink.style.opacity = '1';
304            
305            // Remove any premium locks or overlays
306            const lockIcons = practiceHubLink.querySelectorAll('[class*="lock"], [class*="premium"]');
307            lockIcons.forEach(icon => icon.remove());
308        }
309
310        // Find all practice buttons and unlock them
311        const practiceButtons = document.querySelectorAll('[data-test*="practice"], button[aria-label*="Practice"], button[aria-label*="Práctica"]');
312        practiceButtons.forEach(button => {
313            console.log('Unlocking practice button:', button);
314            button.style.pointerEvents = 'auto';
315            button.style.opacity = '1';
316            button.removeAttribute('disabled');
317            button.classList.remove('locked', 'disabled');
318        });
319
320        // Enable practice hub in user data
321        if (window.duo && window.duo.user) {
322            window.duo.user.hasPracticeHub = true;
323            window.duo.user.practiceHubUnlocked = true;
324            console.log('Enabled practice hub in duo.user');
325        }
326
327        if (window.duoState && window.duoState.user) {
328            window.duoState.user.hasPracticeHub = true;
329            window.duoState.user.practiceHubUnlocked = true;
330            console.log('Enabled practice hub in duoState.user');
331        }
332
333        // Add CSS to unlock practice features
334        TM_addStyle(`
335            /* Unlock practice hub */
336            [data-test="practice-hub-nav"],
337            [data-test*="practice"] {
338                pointer-events: auto !important;
339                opacity: 1 !important;
340            }
341            
342            /* Remove practice locks */
343            [data-test*="practice"] [class*="lock"],
344            [data-test*="practice"] [class*="premium"] {
345                display: none !important;
346            }
347        `);
348    }
349
350    // Function to restore old profile picture system
351    function restoreOldProfilePictures() {
352        console.log('Restoring old profile picture system...');
353
354        // Enable old profile picture system in user data
355        if (window.duo && window.duo.user) {
356            window.duo.user.useOldProfilePictures = true;
357            window.duo.user.hasCustomAvatar = true;
358            window.duo.user.canChangeAvatar = true;
359            console.log('Enabled old profile pictures in duo.user');
360        }
361
362        if (window.duoState && window.duoState.user) {
363            window.duoState.user.useOldProfilePictures = true;
364            window.duoState.user.hasCustomAvatar = true;
365            window.duoState.user.canChangeAvatar = true;
366            console.log('Enabled old profile pictures in duoState.user');
367        }
368
369        // Try to modify profile picture settings in localStorage
370        try {
371            const profileSettings = localStorage.getItem('profileSettings');
372            if (profileSettings) {
373                const settings = JSON.parse(profileSettings);
374                settings.useOldProfilePictures = true;
375                settings.hasCustomAvatar = true;
376                localStorage.setItem('profileSettings', JSON.stringify(settings));
377                console.log('Updated profile settings in localStorage');
378            }
379        } catch (e) {
380            console.log('Could not update profile settings:', e);
381        }
382
383        // Add CSS to show old profile picture options
384        TM_addStyle(`
385            /* Show old profile picture options */
386            [data-test="profile-avatar-selector"],
387            [class*="avatar-selector"],
388            [class*="profile-picture-selector"] {
389                display: block !important;
390                visibility: visible !important;
391            }
392            
393            /* Remove new profile picture restrictions */
394            [class*="avatar-premium"],
395            [class*="avatar-lock"] {
396                display: none !important;
397            }
398        `);
399    }
400
401    // Debounce function to avoid excessive calls
402    function debounce(func, wait) {
403        let timeout;
404        return function executedFunction(...args) {
405            const later = () => {
406                clearTimeout(timeout);
407                func(...args);
408            };
409            clearTimeout(timeout);
410            timeout = setTimeout(later, wait);
411        };
412    }
413
414    // Debounced version of removeAdsAndPrompts
415    const debouncedRemoveAds = debounce(removeAdsAndPrompts, 500);
416
417    // Initialize when DOM is ready
418    function init() {
419        console.log('Initializing Duolingo Ad Remover...');
420        
421        // Initial cleanup
422        removeAdsAndPrompts();
423        unlockPremiumFeatures();
424
425        // Watch for DOM changes to remove dynamically loaded ads
426        const observer = new MutationObserver(debouncedRemoveAds);
427        
428        observer.observe(document.body, {
429            childList: true,
430            subtree: true
431        });
432
433        console.log('Duolingo Ad Remover initialized successfully!');
434    }
435
436    // Run when body is ready
437    TM_runBody(init);
438
439    // Also run periodically to catch any missed ads
440    setInterval(() => {
441        removeAdsAndPrompts();
442        unlockPremiumFeatures();
443    }, 5000);
444
445})();
Duolingo Ad Remover with Plus Features | Robomonkey