15 minutes to create a personal assistant that can search on wikipedia (and tell some horrible jokes)

leonardbonetti

Leonardo Bonetti

Posted on March 2, 2021

15 minutes to create a personal assistant that can search on wikipedia (and tell some horrible jokes)

Introduction

On a boring day at work, a colleague and I were talking to Alexa, when a bet arose due to the joke, creating a virtual assistant in 15 minutes.

Alexa has a multitude of functions, so to make the challenge possible ours should just take questions about common topics.

Then we choose the following rules:

  • The assistant must interact with you by speaking your name
  • Responses must be in audio
  • Searches must be carried out using wikipedia
  • Tell some jokes

Let's code

Create project

I can't code without typescript anymore, so...

npm init -y && npx ts-init
Enter fullscreen mode Exit fullscreen mode

Check that your tsconfig.json file is as follows

{
  "compilerOptions": {
    "lib": [
      "es6",
      "DOM"
    ],
    "alwaysStrict": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "resolveJsonModule": true
  },
  "files": [
    "src/index.ts"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Lets get our packages

  • npm i --save say Say is an amazing TTS (Text-to-Speech) library, it will help us with a voice to our robot.

  • npm i --save wikipedia Wikipedia is a simple lib that abstract the wiki endpoints and make our lives easier

Add some Jokes

Create a file named src/jokes.json, copie the content of this gist to your file

Finally we will code

Inside your src/index.ts import all libraries

import say from 'say';
import wikipedia from "wikipedia";
import jokes from './jokes.json';
import readline from "readline";

...
Enter fullscreen mode Exit fullscreen mode

Our personal assistant will receive the commands by text, like the old ways
So, w'll need a interface to interact with our user by terminal

...

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});
Enter fullscreen mode Exit fullscreen mode

The main goal here is create an assistant that can make searchs on wikipedia, so, lets create this func

...
const wikisearch = async (topic: string) => {
    const search = await wikipedia.search(topic);
    const page = await wikipedia.page(search.results[0].title);

    const summary = await page.summary();

    return summary.extract;
}
Enter fullscreen mode Exit fullscreen mode

Now let's give our assistant a voice

...
const speak = (text: string) => {
    return new Promise((resolve, reject) => {
        say.speak(text, 'Samantha', 0.9, (err) => {
            if(err) reject(err);
            resolve(true);
        });
    })
}
Enter fullscreen mode Exit fullscreen mode

As my assistant is a woman, I decided to give her the voice of Samantha, and to make it more audible, I leave the playback speed at 0.9, but feel free to change any of these properties, you can check the full list of voices available here

Ok, lets interact.
By the first rule of the challenge, the bot need to know our name, so

...

let name = "";

rl.question("What is your name ? ", async function(received: string) {
        name = received;
        await speak(`Hello ${received}, my name is Clotilde`);
        ask();
});
Enter fullscreen mode Exit fullscreen mode

Now, lets code the final function

...
const ask = () => {
    rl.question("ask me something: ", async function(rQuery: string) {

        if(rQuery == 'stop') {
            say.stop();
            return rl.close();
        }

        if(rQuery.toLocaleLowerCase().split(' ').findIndex(item => item == 'joke') > -1) {
            const jokeIndex = Math.floor(Math.random() * jokes.length);
            const joke = jokes[jokeIndex];
            await speak(joke.text);
        } else {
            const searchTopic = await wikisearch(rQuery);
            await speak(`According to wikipedia, ${searchTopic}`);
        }

        ask();
    });
}
Enter fullscreen mode Exit fullscreen mode

The idea here is very simple, if the user asks something that contains the word joke, we tell a joke, if not, we search on wikipedia.

And, for finally, when we shutdown our assistant:

...

rl.on("close", async function() {
    console.log("\nBYE BYE !!!");
    await speak("Bye Bye");
    process.exit(0);
});
Enter fullscreen mode Exit fullscreen mode

She will say "Bye Bye"

Your code should now look like this:

import say from 'say';
import wikipedia from "wikipedia";
import jokes from './jokes.json';
import readline from "readline";

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

const wikisearch = async (topic: string) => {
    const search = await wikipedia.search(topic);
    const page = await wikipedia.page(search.results[0].title);

    const summary = await page.summary();

    return summary.extract;
}

const speak = (text: string) => {
    return new Promise((resolve, reject) => {
        say.speak(text, 'Samantha', 0.9, (err) => {
            if(err) reject(err);
            resolve(true);
        });
    })
}

let name = "";

rl.question("What is your name ? ", async function(received: string) {
        name = received;
        await speak(`Hello ${received}, my name is Clotilde`);
        ask();
});


const ask = () => {
    rl.question("ask me something: ", async function(rQuery: string) {

        if(rQuery == 'stop') {
            say.stop();
            return rl.close();
        }

        if(rQuery.toLocaleLowerCase().split(' ').findIndex(item => item == 'joke') > -1) {
            const jokeIndex = Math.floor(Math.random() * jokes.length);
            const joke = jokes[jokeIndex];
            await speak(joke.text);
        } else {
            const searchTopic = await wikisearch(rQuery);
            await speak(`According to wikipedia, ${searchTopic}`);
        }

        ask();
    });
}


rl.on("close", async function() {
    console.log("\nBYE BYE !!!");
    await speak("Bye Bye");
    process.exit(0);
});
Enter fullscreen mode Exit fullscreen mode

The complete repo of this project can be found on here

Thank you very much, let us know in the comments section how long it took you to create this bot

💖 💪 🙅 🚩
leonardbonetti
Leonardo Bonetti

Posted on March 2, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related