4️⃣

JavaScript 20 Projects - 4 Joke Teller

2023/08/18に公開

前言

這系列是課程 JavaScript Web Projects: 20 Projects to Build Your Portfolio 的筆記,學習利用 Javascript 做出各種互動網站。

以下是這系列的文章:

Project 1 Quote Generator
Project 2 Infinity Scroll
Project 3 Picture In Picture
Project 4 Joke Teller

目標

這次會利用語音 API (Text-to-speech API) 跟 JokeAPI ,來實作讓電腦隨機說笑話。

下面是這次要實作的畫面。範例的連結

Image.png

建立 HTML

主要就是 <button> 以及 <audio> 兩個部分。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Joke Teller</title>
    <link rel="icon" type="image/png" href="favicon.png">
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <button id="button">Tell Me A Joke</button>    
        <audio id="audio" controls hidden></audio>   
    </div>
    <!-- Script -->
    <script src="voiceRSS.js"></script>
    <script src="script.js"></script>
</body>
</html>

<audio> 沒有隱藏的話會顯示出播放的部分,如下圖。

Image.png

思考 javascript 所需要的動作

  1. 如何使用 Text-to-speech API ?
  2. 如何使用 Joker API ?
  3. 整合在一起

動作解讀

如何使用 Text-to-speech API ?

  1. 這次 Text-to-speech API 使用 Voice RSS。註冊後就可以取得一個 API Key。
  2. 取得 Text-to-Speech JavaScript SDK 所提供下載的 JavaScript 文件,以及 reference 。

Image.png

Image.png

  1. 修改下載的 JavaScript 文件。
    • 原本的檔案內容沒有辦法傳到 <audio> element,使得無法對它多做控制。
    • 刪除原本的 new Audio(t.responseText).play();
    • 增加 audioElement.src = t.responseText; audioElement.play();

Udemy ScreenShot 2023-08-18 11-32-11.png

const button = document.getElementById("button");
const audioElement = document.getElementById("audio");

// VoiceRSS Javascript SDK
const VoiceRSS={speech(e){this._validate(e),this._request(e)},_validate(e){if(!e)throw"The settings are undefined";if(!e.key)throw"The API key is undefined";if(!e.src)throw"The text is undefined";if(!e.hl)throw"The language is undefined";if(e.c&&"auto"!=e.c.toLowerCase()){let a=!1;switch(e.c.toLowerCase()){case"mp3":a=(new Audio).canPlayType("audio/mpeg").replace("no","");break;case"wav":a=(new Audio).canPlayType("audio/wav").replace("no","");break;case"aac":a=(new Audio).canPlayType("audio/aac").replace("no","");break;case"ogg":a=(new Audio).canPlayType("audio/ogg").replace("no","");break;case"caf":a=(new Audio).canPlayType("audio/x-caf").replace("no","")}if(!a)throw`The browser does not support the audio codec ${e.c}`}},_request(e){const a=this._buildRequest(e),t=this._getXHR();t.onreadystatechange=function(){if(4==t.readyState&&200==t.status){if(0==t.responseText.indexOf("ERROR"))throw t.responseText;let e=t.responseText;audioElement.src=e,audioElement.onloadedmetadata=(()=>{audioElement.play()})}},t.open("POST","https://api.voicerss.org/",!0),t.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8"),t.send(a)},_buildRequest(e){const a=e.c&&"auto"!=e.c.toLowerCase()?e.c:this._detectCodec();return`key=${e.key||""}&src=${e.src||""}&hl=${e.hl||""}&r=${e.r||""}&c=${a||""}&f=${e.f||""}&ssml=${e.ssml||""}&b64=true`},_detectCodec(){const e=new Audio;return e.canPlayType("audio/mpeg").replace("no","")?"mp3":e.canPlayType("audio/wav").replace("no","")?"wav":e.canPlayType("audio/aac").replace("no","")?"aac":e.canPlayType("audio/ogg").replace("no","")?"ogg":e.canPlayType("audio/x-caf").replace("no","")?"caf":""},_getXHR(){try{return new XMLHttpRequest}catch(e){}try{return new ActiveXObject("Msxml3.XMLHTTP")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.6.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP.3.0")}catch(e){}try{return new ActiveXObject("Msxml2.XMLHTTP")}catch(e){}try{return new ActiveXObject("Microsoft.XMLHTTP")}catch(e){}throw"The browser does not support HTTP request"}};

// VoiceRSS Speech Function
function test() {
  VoiceRSS.speech({
      key: '<API key>',
      src: 'Hello, world!',
      hl: 'en-us',
      v: 'Linda',
      r: 0, 
      c: 'mp3',
      f: '44khz_16bit_stereo',
      ssml: false
  });
}

如何使用 Joker API ?

  1. 利用連結可以取得 Joker API 的 Url。

Image.png

  1. 利用 Url 取得資料,並把 joke 設為 empty string。
  2. 因為 joke 有分 single 以及 twopart,所以要先確認為哪一種形式,再設定給 joke。
// Get jokes from Joke API
async function getJokes() {
  let joke = "";
  const apiUrl =
    "https://sv443.net/jokeapi/v2/joke/Programming?blacklistFlags=nsfw,racist,sexist";
  try {
    const response = await fetch(apiUrl);
    const data = await response.json();
    // Assign One or Two Part Joke
    if (data.setup) {
      joke = `${data.setup} ... ${data.delivery}`;
    } else {
      joke = data.joke;
    }
  } catch (error) {
    // Catch Error Here
  }
}

整合在一起

  1. 把原本的 test() function 改成 tellMe(),並在 getJokes() 中設定 joke 完後來呼叫tellMe()
  2. tellMe() 裡多加const jokeString = joke.trim().replace(/ /g, "%20”);,來去除字串前後的空格,以及把字串中的所有空格替換成 "%20”,確保URL 中編碼空格時使用的方式。
    • joke.trim(): trim() 方法用於去除字串的前後空白(包括空格、換行符等)。
    • replace(/ /g, "%20"): replace() 方法用於將字串中選定的字串替換成另一個字串。在這個程式碼中,使用了正則表達式 / /g 來表示所有的空格(" "),並把他替換成 "%20"。在 URL 編碼中,"%20" 代表空格。
  3. 設置 toggleButton() 來防止 button 在載入資料時有其他動作。
  4. 設定 button 以及 audioElement 的 Event Listeners。
const button = document.getElementById("button");
const audioElement = document.getElementById("audio");

// Disable/Enable Button
function toggleButton() {
  button.disabled = !button.disabled;
}

// VoiceRSS Speech Function
function tellMe(joke) {
  const jokeString = joke.trim().replace(/ /g, "%20");
  // VoiceRSS Speech Parameters
  VoiceRSS.speech({
    key: "API Key",
    src: jokeString,
    hl: "en-us",
    r: 0,
    c: "mp3",
    f: "44khz_16bit_stereo",
    ssml: false,
  });
}

// Get jokes from Joke API
async function getJokes() {
  let joke = "";
  const apiUrl =
    "https://sv443.net/jokeapi/v2/joke/Programming?blacklistFlags=nsfw,racist,sexist";
  try {
    const response = await fetch(apiUrl);
    const data = await response.json();
    // Assign One or Two Part Joke
    if (data.setup) {
      joke = `${data.setup} ... ${data.delivery}`;
    } else {
      joke = data.joke;
    }
    // Passing Joke to VoiceRSS API
    tellMe(joke);
    // Disable Button
    toggleButton();
  } catch (error) {
    // Catch Error Here
  }
}

// Event Listeners
button.addEventListener("click", getJokes);
audioElement.addEventListener("ended", toggleButton);

Discussion