15 minutes to create a personal assistant that can search on wikipedia (and tell some horrible jokes)
Tags: javascript, node, programming, funny
Published: 2021-03-02 14:32:42

#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
```
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"
  ]
}
```

###Lets get our packages
* `npm i --save say` [Say](https://www.npmjs.com/package/say) is an amazing TTS (Text-to-Speech) library, it will help us with a voice to our robot.

* `npm i --save wikipedia` [Wikipedia](https://www.npmjs.com/package/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](https://gist.githubusercontent.com/LeonBonetti/6101b34414761fc4567acb2eb930598a/raw/2454af2e5c362c58b6dd2397bc91fa6c4377bd2f/jokes.json) 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";

...
```

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

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

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](https://github.com/Marak/say.js/blob/master/examples/osx-allvoices.js)

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

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

The complete repo of this project can be found on [here](https://github.com/LeonBonetti/Clotilde-the-personal-assistant)

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