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})();