1/** 2 * @file 3 * Plays Image CAPTCHA characters via the Web Speech API. 4 * 5 * Speaks values like: "capital A, small b, number 3". 6 */ 7 8(function ($, Drupal, once) { 9 "use strict"; 10 11 const getCaptchaForm = ($el) => $el.closest("form"); 12 13 const getSessionData = ($form) => ({ 14 sid: String($form.find('input[name="captcha_sid"]').val() || ""), 15 token: String($form.find('input[name="captcha_token"]').val() || ""), 16 }); 17 18 const buildSpeakUrl = (button, sid) => { 19 const template = 20 button.getAttribute("data-audio-url-template") || 21 "/captcha-audio/123456789"; 22 return template.replace("123456789", encodeURIComponent(sid)); 23 }; 24 25 const speakText = (text) => { 26 if ( 27 !window.speechSynthesis || 28 typeof window.SpeechSynthesisUtterance === "undefined" 29 ) { 30 return false; 31 } 32 33 window.speechSynthesis.cancel(); 34 const utterance = new window.SpeechSynthesisUtterance(text); 35 utterance.rate = 0.85; 36 utterance.pitch = 1; 37 utterance.lang = document.documentElement.lang || "en-IN"; 38 window.speechSynthesis.speak(utterance); 39 return true; 40 }; 41 42 Drupal.behaviors.captchaAudioPlay = { 43 attach: function (context) { 44 once("captcha-audio-play", ".captcha-audio-play", context).forEach( 45 function (button) { 46 const $button = $(button); 47 48 $button.on("click", function (event) { 49 event.preventDefault(); 50 51 const $form = getCaptchaForm($button); 52 const session = getSessionData($form); 53 54 if (!session.sid || !session.token) { 55 window.alert( 56 Drupal.t("CAPTCHA audio is not available right now.") 57 ); 58 return; 59 } 60 61 if (!window.speechSynthesis) { 62 window.alert( 63 Drupal.t( 64 "Audio playback is not supported in this browser. Please enter the characters you see in the image." 65 ) 66 ); 67 return; 68 } 69 70 $button.prop("disabled", true).attr("aria-busy", "true"); 71 72 const url = 73 buildSpeakUrl(button, session.sid) + 74 "?token=" + 75 encodeURIComponent(session.token); 76 77 $.getJSON(url) 78 .done(function (response) { 79 if (!response || response.status !== 1 || !response.speech) { 80 window.alert( 81 (response && response.message) || 82 Drupal.t("Unable to play CAPTCHA audio.") 83 ); 84 return; 85 } 86 87 if (!speakText(response.speech)) { 88 window.alert( 89 Drupal.t( 90 "Audio playback is not supported in this browser. Please enter the characters you see in the image." 91 ) 92 ); 93 } 94 }) 95 .fail(function () { 96 window.alert(Drupal.t("Unable to play CAPTCHA audio.")); 97 }) 98 .always(function () { 99 $button.prop("disabled", false).removeAttr("aria-busy"); 100 }); 101 }); 102 } 103 ); 104 }, 105 }; 106})(jQuery, Drupal, once);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.