Saving a lot of time during the COVID-19 shutdown gave me the chance to do something I wanted to try for a while, getting my Google Home Mini to tell me the prayer time instead of looking at a screen. So this weekend I got to play with a dummy piece of code I made during the week and finalize my test version of my own prayer times assistant.
So far, I can only ask about the next prayer or name a specific prayer to know its time.
Important note: you cannot replicate the following project without a premium account on Firebase to enable calls to external APIs.
Here is what I had to do in simple words:
Dialogflow is where I started my project with creating a blank agent with nothing but a Welcome intent.
For the sake of having fun, I changed the way of addressing the user to say the magic word: "Salam! How can I help you?" plus accepting similar magic words like "Assalm Alaykom".
This intent is meant to looks for the next prayer time in the array of today's prayers. But first, in which city or location?
To get the user's location, I need to ask for permissions. I didn't want one extra step to ask for permission. I actually don't know if I will have to do this every time or I can add checks once and then I have the location for always. But to cut the working time, I decided to let the user simply mention the city. This can make it faster to develop my agent for now. So I added some training phrases with and without the city parameter. But notice that I added the city parameter as required and added some prompts to ask the user if the parameter is not found in the phrase. I also helped the training by double-clicking the word "Berlin" and chose my city parameter from that popup list which appeared.
To get to write some code to get the answer I need, I should enable fulfillments (enable webhooks checkbox). This is a special section where you can programmatically manipulate your agent. I also marked this intent as an end of conversation. This means that once I respond, the agent stops and does not listen to any more input from the user.
I will get to the fulfillments section a bit later.
If I already can fetch and scan through some prayer times, creating the next intent was pretty easy in comparison to the previous one. That's because I includes no comparisons or calculations, but as simple as looking for a specific key in the prayers key-value object.
To detect the prayer in the user's phrase, I had to create a special entity which does not exist by default, which is the prayer entity.
This entity can be created from the left side panel. In the entity, I define the possible entries, along with any synonyms which can mean the same thing. So I added the names of the five prayers and added some synonyms for any different pronunciation.
Just like the previous intent, I created a new one and added some training phrases and responses. And this time I added one more parameter: prayer. and I set its entity as Prayer, which I just created. And then I marked the prayer in each training phrase by marking the text and choosing the correct parameter from the list appearing.
This step is similar to the previous entity. I enabled fulfillments and marked the intent as end of conversation.
Now comes the programmatic part. In this section, we should enable the inline editor. This is where we write handlers to do what we want. There is another option to have webhooks written with some standards. But lets stick to the quick win of scripting something. Remember that you need a premium Firebase account to do this step to be able to call external APIs.
The code here is as simple as writing a handler and registering this handler to a specific intent by matching its name. So the skeleton of my code looks like this:
This skeleton blends with the existing boilerplate in the inline editor. The content of my functions is where magic happens. I wrote some quick and dirty code based on the first API I found in my search for prayer times APIs. And spent a good amount of time fiddling with the rabbit hole of JS date objects and local vs UTC mess.
Here is my code if you are curious or want to make your own cleaner code :)
On the right side of the screen, there is a message saying;
And from the Actions Console you can modify some settings and deploy your app to alpha and beta versions, or even submit it to be reviewed and publicly published.
That's it! I created a simple app to help me with my daily need. And I learned something new along the way. I have more plans to improve my project to my needs and maybe publish it later to others.
I hope you learned something from my post. :)
So far, I can only ask about the next prayer or name a specific prayer to know its time.
Important note: you cannot replicate the following project without a premium account on Firebase to enable calls to external APIs.
Here is what I had to do in simple words:
1- Create a new Dialogflow agent
Dialogflow is where I started my project with creating a blank agent with nothing but a Welcome intent.
2 - Modify the default welcome intent
For the sake of having fun, I changed the way of addressing the user to say the magic word: "Salam! How can I help you?" plus accepting similar magic words like "Assalm Alaykom".
3 - Create a new intent for the next prayer time
This intent is meant to looks for the next prayer time in the array of today's prayers. But first, in which city or location?
Circumvent the location access step
To get the user's location, I need to ask for permissions. I didn't want one extra step to ask for permission. I actually don't know if I will have to do this every time or I can add checks once and then I have the location for always. But to cut the working time, I decided to let the user simply mention the city. This can make it faster to develop my agent for now. So I added some training phrases with and without the city parameter. But notice that I added the city parameter as required and added some prompts to ask the user if the parameter is not found in the phrase. I also helped the training by double-clicking the word "Berlin" and chose my city parameter from that popup list which appeared.
Use fulfillment for fetching prayer times
To get to write some code to get the answer I need, I should enable fulfillments (enable webhooks checkbox). This is a special section where you can programmatically manipulate your agent. I also marked this intent as an end of conversation. This means that once I respond, the agent stops and does not listen to any more input from the user.
I will get to the fulfillments section a bit later.
4 - Create a new intent for a specific prayer time
If I already can fetch and scan through some prayer times, creating the next intent was pretty easy in comparison to the previous one. That's because I includes no comparisons or calculations, but as simple as looking for a specific key in the prayers key-value object.
Create a Prayer entity
To detect the prayer in the user's phrase, I had to create a special entity which does not exist by default, which is the prayer entity.
This entity can be created from the left side panel. In the entity, I define the possible entries, along with any synonyms which can mean the same thing. So I added the names of the five prayers and added some synonyms for any different pronunciation.
Create the intent with the phrases and responses like before
Just like the previous intent, I created a new one and added some training phrases and responses. And this time I added one more parameter: prayer. and I set its entity as Prayer, which I just created. And then I marked the prayer in each training phrase by marking the text and choosing the correct parameter from the list appearing.
Enable fulfillment and end of conversation
This step is similar to the previous entity. I enabled fulfillments and marked the intent as end of conversation.
5 - Fulfillment section
Now comes the programmatic part. In this section, we should enable the inline editor. This is where we write handlers to do what we want. There is another option to have webhooks written with some standards. But lets stick to the quick win of scripting something. Remember that you need a premium Firebase account to do this step to be able to call external APIs.
The code here is as simple as writing a handler and registering this handler to a specific intent by matching its name. So the skeleton of my code looks like this:
function nextPrayerTimeHandler(agent) { // some code here agent.add(`This is something the agent is going to say to the user!`); } function specificPrayerTimeHandler(agent) { // some code here agent.add(`This is something the agent is going to say to the user!`); } intentMap.set('NextPrayerIntent', nextPrayerTimeHandler); intentMap.set('SpecificPrayerIntent', specificPrayerTimeHandler);
This skeleton blends with the existing boilerplate in the inline editor. The content of my functions is where magic happens. I wrote some quick and dirty code based on the first API I found in my search for prayer times APIs. And spent a good amount of time fiddling with the rabbit hole of JS date objects and local vs UTC mess.
Here is my code if you are curious or want to make your own cleaner code :)
const https = require("https"); const apiBaseTodayPrayers = 'https://api.pray.zone/v2/times/today.json?higher-latitudes=3&school=3&city='; const prayerKeys = ['Fajr', 'Dhuhr', 'Asr', 'Maghrib', 'Isha']; var city = ''; var prayer = ''; function constructTimestampFromData(date, time, offset) { var offsetSign = (offset > 0) ? '+' : '-'; var fullTimeString = `${date} ${time} GMT${offsetSign}${offset}`; return (new Date(fullTimeString).getTime()); } function findNextPrayerTimeFromJsonResponse(jsonData) { let allPrayerTimes = jsonData.results.datetime[0].times; let date = jsonData.results.datetime[0].date.gregorian; let offset = jsonData.results.location.local_offset; // Filter prayer times and convert from string to timestamp var filteredPrayerTimes = {}; for (var timeKey in allPrayerTimes) { if (prayerKeys.includes(timeKey)) { filteredPrayerTimes[timeKey] = constructTimestampFromData(date, allPrayerTimes[timeKey], offset); } } // Get local time let now = new Date().getTime(); // Find the next prayer var nextPrayerName = ''; var nextPrayerTime = ''; for (var key in filteredPrayerTimes) { if (filteredPrayerTimes[key] > now) { return [key, allPrayerTimes[key]]; } } } function nextPrayerTimeHandler(agent) { // Get the current city city = agent.parameters.city; // Make API call to fetch prayer times const todayPrayersUrl = apiBaseTodayPrayers + city; return new Promise((resolve, reject) => { https.get(todayPrayersUrl, function(resp) { var json = ""; resp.on("data", function(chunk) { json += chunk; }); resp.on("end", function() { let jsonData = JSON.parse(json); let nextPrayerNameAndTime = findNextPrayerTimeFromJsonResponse(jsonData); // Respond if (nextPrayerNameAndTime[0] === '') { agent.add(`There are no more prayers in ${city} today.`); } else { agent.add(`The next prayer in ${city} is ${nextPrayerNameAndTime[0]} at ${nextPrayerNameAndTime[1]}.`); } resolve(); }); }); }); } function specificPrayerTimeHandler(agent) { // Get the current city city = agent.parameters.city; // Get the prayer name prayer = agent.parameters.prayer; const todayPrayersUrl = apiBaseTodayPrayers + city; return new Promise((resolve, reject) => { https.get(todayPrayersUrl, function(resp) { var json = ""; resp.on("data", function(chunk) { json += chunk; }); resp.on("end", function() { let jsonData = JSON.parse(json); let prayerTime = jsonData.results.datetime[0].times[prayer]; // Respond if (prayerTime === undefined) { agent.add(`Sorry, I cound not find ${prayer} time for ${city}.`); } else { agent.add(`${prayer} in ${city} is at ${prayerTime}.`); } resolve(); }); }); }); } intentMap.set('NextPrayerIntent', nextPrayerTimeHandler); intentMap.set('SpecificPrayerIntent', specificPrayerTimeHandler);
6 - Testing with Google Assistant
On the right side of the screen, there is a message saying;
Click it and you will be directed to Actions Console where you can test against a simulator for Google Assistant as well as other Google devices. You can also test on your own Google Home if you are connected to the same account from Google Home.See how it works in Google Assistant.
And from the Actions Console you can modify some settings and deploy your app to alpha and beta versions, or even submit it to be reviewed and publicly published.
Live demo
That's it! I created a simple app to help me with my daily need. And I learned something new along the way. I have more plans to improve my project to my needs and maybe publish it later to others.
I hope you learned something from my post. :)
Resources
- LinkedIn Learning course: Building Chatbots Using Google Dialogflow
- Prayer times API