Duolingo Text-to-Speech for answers without audio

Based on https://greasyfork.org/en/scripts/498105-duolingo-hebrew-text-to-speech/code try to do the same thing as the original script but auto detecting the language

  1. // ==UserScript==
  2. // @name Duolingo Text-to-Speech for answers without audio
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description Based on https://greasyfork.org/en/scripts/498105-duolingo-hebrew-text-to-speech/code try to do the same thing as the original script but auto detecting the language
  6. // @author justsomelearner
  7. // @match https://www.duolingo.com/*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13. // https://stackoverflow.com/questions/7089443/restoring-console-log
  14. // https://stackoverflow.com/questions/46111545/console-log-doesn-t-print-anything-but-returns-undefined
  15. var i = document.createElement('iframe');
  16. i.style.display = 'none';
  17. document.body.appendChild(i);
  18. window.console = i.contentWindow.console;
  19.  
  20. var debug = () => {};
  21. //var debug = console.log;
  22. debug("Duolingo SpeakText Text-to-Speech script loaded");
  23.  
  24. // Check if the browser supports the Web Speech API for TTS
  25. if (!('speechSynthesis' in window)) {
  26. alert("Your browser does not support the Web Speech API for text-to-speech");
  27. return;
  28. }
  29.  
  30. let lastReadText = ''; // Variable to store the last read text
  31.  
  32. // Function to read the SpeakText text aloud
  33. function readText(text, language) {
  34. const utterance = new SpeechSynthesisUtterance(text);
  35. utterance.lang = language;
  36. utterance.rate = 0.8;
  37. speechSynthesis.speak(utterance);
  38. debug("Reading text: ", text);
  39. }
  40.  
  41. // Function to add a TTS button next to SpeakText text
  42. function addTTSButton() {
  43. debug("Attempting to add TTS button");
  44.  
  45. // Select the SpeakText text element
  46. const hebrewTextElement = document.querySelector('#root > div.kPqwA._2kkzG > div._3yE3H > div > div.RMEuZ._1GVfY > div > div > div > div > div._2hpO2.UjFh4._3rat3 > div._1RjNT._3v0hd > div > div.rPXvv._44BHt > div > div');
  47. debug("SpeakText text element: ", hebrewTextElement);
  48.  
  49. if (hebrewTextElement && !document.getElementById('ttsButton')) {
  50. const ttsButton = document.createElement('button');
  51. const language = hebrewTextElement.getAttribute('lang');
  52. debug(`Language : ${language}`);
  53.  
  54. ttsButton.id = 'ttsButton';
  55. ttsButton.innerText = '🔊';
  56. ttsButton.style = 'background-color: #1cb0f6; color: white; border: none; padding: 10px; margin-left: 10px; cursor: pointer; border-radius: 50%; width: 30px; height: 30px;';
  57. ttsButton.onclick = () => readText(hebrewTextElement.textContent, language);
  58. hebrewTextElement.parentElement.appendChild(ttsButton);
  59. debug("TTS button added");
  60. } else {
  61. debug("SpeakText text element not found or TTS button already exists");
  62. }
  63. }
  64.  
  65. // Function to find and read the SpeakText text automatically
  66. function readSpeakText() {
  67. debug("Attempting to read SpeakText text");
  68.  
  69. // Select the SpeakText text element
  70. const hebrewTextElement = document.querySelector('#root > div.kPqwA._2kkzG > div._3yE3H > div > div.RMEuZ._1GVfY > div > div > div > div > div._34aEz._1IiFg.f7WE2._3rat3 > div.aMPis._35mGI > span > span > span');
  71. debug("SpeakText text element: ", hebrewTextElement);
  72.  
  73. if (hebrewTextElement) {
  74. const text = hebrewTextElement.textContent;
  75. if (text !== lastReadText) {
  76. readText(text);
  77. lastReadText = text; // Update the last read text
  78. } else {
  79. debug("Text has already been read");
  80. }
  81. } else {
  82. debug("SpeakText text element not found");
  83. }
  84. }
  85.  
  86. // Observe changes in the Duolingo interface to read the text when it changes
  87. const observer = new MutationObserver((mutations) => {
  88. mutations.forEach((mutation) => {
  89. if (mutation.addedNodes.length || mutation.removedNodes.length) {
  90. debug("Mutation observed");
  91. readSpeakText();
  92. addTTSButton();
  93. }
  94. });
  95. });
  96.  
  97. observer.observe(document.body, { childList: true, subtree: true });
  98.  
  99. // Initial call to read the text if it's already there and add the button
  100. window.addEventListener('load', () => {
  101. debug("Page loaded");
  102. readSpeakText();
  103. addTTSButton();
  104. });
  105.  
  106. })();