I am following the answer of #dstoiko from here
I am calling the API in ADD_MOVIE block and want to pass some value to my postback with payload ADD_TO_FIREBASE
here is my blocks
'use strict';
const Script = require('smooch-bot').Script;
var YtsHelper = require('./libs/YtsHelper.js');
const FirebaseHelper = require('./libs/FirebaseHelper.js');
var firebaseHelperObj = new FirebaseHelper();
module.exports = new Script({
processing: {
prompt: (bot) => bot.say('Beep boop...'),
receive: () => 'processing'
},
start: {
receive: (bot) => {
return bot.say('Hi! I\'m Smooch Bot!')
.then(() => 'showUserMenu');
}
},
showUserMenu: {
prompt: (bot) => bot.say("Here are the areas I can help you out. %[Add Movie](postback:ADD_MOVIE) %[Serve Food](postback:SERVE_FOOD)"),
receive: () => 'finish'
},
ADD_MOVIE : {
prompt: (bot) => bot.say('Enter movie name or keywords you want to search please.'),
receive: (bot, message) => {
const movie_name_searched = message.text;
return bot.setProp('movie_name_searched', movie_name_searched)
.then(() => bot.say('Search in progress...'))
.then(() => {
YtsHelper.getMoviesList(movie_name_searched,function(movies_array){
var movies_postbacks = "";
console.log("Movies SIZE " + movies_array.length);
for (var i = 0; i < movies_array.length ; i++){
movies_postbacks = movies_postbacks + " %["+movies_array[i]+"](postback:ADD_TO_FIREBASE)";
}
bot.say(movies_postbacks)
.then(() => bot.say("Click any movie to add into firebase."));
});
});
}
},
ADD_TO_FIREBASE: {
prompt: (bot) => bot.say("confirm, y/n"),
receive: () => 'showUserMenu'
},
finish: {
receive: (bot, message) => {
return bot.getProp('name')
.then((name) => bot.say(`Sorry ${name}, my creator didn't ` +
'teach me how to do anything else!'))
.then(() => 'showUserMenu');
}
}
});
Questions
Q0. I am new to nodeJS also, What should I call ADD_MOVIE, start, showUserMenu (in my code) blocks? function, method, code, module etc.
Q1. I Have called an yts api in my ADD_MOVIE block. Is this fine to call API in script.js file?
Q2. Important!: How can I pass the param to my postback with payload ADD_MOVIE so that I can perform some conditional code in ADD_TO_FIREBASE block
Q0: is a question of style, there's no definitive answer to give here. In other words, this is the wrong forum for this kind of discussion :) https://stackoverflow.com/help/how-to-ask
Q1: Yes making a DB query in receive is fine, however your receive function isn't waiting for the query to finish before it resolves your bot state. If for example you don't want your bot to accept user input until after the movie list is returned, you could do this:
receive: (bot, message) => {
const movie_name_searched = message.text;
return bot.setProp('movie_name_searched', movie_name_searched)
.then(() => bot.say('Search in progress...'))
.then(() => {
return new Promise((res) => YtsHelper.getMoviesList(movie_name_searched, (movies_array) => res(movies_array)));
})
.then((movies_array) => {
var movies_postbacks = "";
for (var i = 0; i < movies_array.length ; i++){
movies_postbacks = movies_postbacks + " %["+movies_array[i]+"](postback:ADD_TO_FIREBASE)";
}
return bot.say(movies_postbacks);
})
.then(() => bot.say("Click any movie to add into firebase."))
.then(() => 'ADD_MOVIE');
}
Note that I'm resolving the very end of the promise chain with 'ADD_MOVIE', which tells your bot to remain in the same state as it was before.
Q2: I see two options.
Option 1: Append the movie ID to the postback payload, eg ADD_TO_FIREBASE.movieid1, ADD_TO_FIREBASE.movieid2 and so on..
If you did this, you would have to define your own behavior inside handlePostback that parses out the movie ID from your postback payload.
You would also have to transition your state amchine into the desired ADD_TO_FIREBASE state yourself. Eg, from your custom handlePostback methdod you would do something like this:
const stateMachine = new StateMachine({
script,
bot: createBot(req.body.appUser)
});
stateMachine.setState('ADD_TO_FIREBASE');
Option 2: The %[foo](postback:bar) message you're using is actually a shorthand syntax. The real inner workings of postback messages are action buttons which you can send to the Smooch API directly. Action buttons also allow you to specify a metadata object. If instead of using the built-in bot.say, you could post messages ot the API directly, and you could store your movie IDs inside the action metadata. You would again have to retrieve the selected movieId from this metadata via your custom handlePostback as you did in option 1.
Related
I have created 3 tables in firebase console. Event, occurrence and venue. Now an event can have multiple occurrences, as occurrences are basically shows.
In occurrence document I have event set as reference and venue set as reference.
I query firebase through occurrence to get all the latest shows.
I want to build an array of javascript objects in react that will have id, date (comes from occurrence), title (comes from event) and address (comes from venue).
const [events, setEvents] = useState<any[]>([]);
useEffect(() => {
const subscribe = firestore()
.collection('occurrences')
.onSnapshot(querySnapshot => {
const showList: any[] = [];
querySnapshot.forEach(documentSnapshot => {
const { startDate, event, venue } = documentSnapshot.data();
const show: any = {
id: documentSnapshot.id,
date: startDate,
};
event.onSnapshot(
eventsDocSS => {
show.title = eventsDocSS.get('name');
},
error => console.error('my error', error)
);
venue.onSnapshot(
eventsDocSS => {
show.address =
eventsDocSS.get('addressLocality') + ', ' + eventsDocSS.get('addressRegion');
},
error => console.error('my error', error)
);
showList.push(show);
});
setEvents(showList);
});
return subscribe()
},[])
This code mind you does not work as I would need to run the event and venue snapshots async to make it work. But if I make it async I have cascade it all the way out at least to forEach loop, which does not work as subscribe cannot be async.
I am using #react-native-firebase/firestore but the code should not be that different in syntax.
How do I get setEvents populated with correct data?
I'm currently making a Discord bot. The code I've provided below is supposed to get data from Hypixel to display guild info. It's getting the correct info, but I want to send all the names as one message instead of one person per message.
This is my code:
const fetch = require('node-fetch');
module.exports = {
name: 'hguild',
aliases: ['hg'],
description: 'Shows info about a hypixel guild!',
guildOnly: true,
args: true,
usage: '<player>',
execute(message, args) {
var ruuid = [];
const guildName = args[0];
message.channel.send('Please wait, checking API').then((msg) => {
fetch(`https://api.hypixel.net/guild?key=[REMOVED]&name=${guildName}`)
.catch((err) => message.channel.send(err))
.then((res) => res.json())
.catch((err) => message.channel.send(err))
.then((json) => {
console.log(json);
msg.edit('Here is about your guild!');
for (const guild of json.guild.members) {
const rawUsername = guild.uuid;
fetch(`https://api.mojang.com/user/profiles/${rawUsername}/names`)
.catch((err) => message.channel.send(err))
.then((res) => res.json())
.catch((err) => message.channel.send(err))
.then((json) => {
console.log(json[0].name);
if (json.name == null || json.status == 'ERR') {
}
var testList = [json[0].name];
message.channel.send(testList);
});
}
});
});
},
};
Currently its showing all the names, but sends one name per message. I want to group all these names together.
Instead of executing these two lines:
var testList = [json[0].name];
message.channel.send(testList);
And then closing the for loop, try pushing the results to an array initialized before the loop, then send that. Example:
var testList = [];
const getUsernames = async () => {
for await (const guild of json.guild.members) {
const rawUsername = guild.uuid;
fetch(`https://api.mojang.com/user/profiles/${rawUsername}/names`)
.catch((err) => message.channel.send(err))
.then((res) => res.json())
.catch((err) => message.channel.send(err))
.then((json) => {
console.log(json[0].name);
if (json.name == null || json.status == 'ERR') {
}
testList.push(json[0].name);
});
}
};
await getUsernames();
message.channel.send(testList.join('\n'));
Make sure you change the execute(message, args) line at the top of your code to async execute(message, args) so that await is possible.
As a disclaimer, I am not knowledgable in the Minecraft API area, and do not know how the object returned from your request is structured. This is just my best guess.
#Lioness100 Also unfamiliar with the Minecraft API.
I'm curious about the loop, seems like your initial fetch to the api with guild info returns what you're looking for.
Are you deconstructing names to test the functionality to eventually push entire profiles to discord? or are you content with just names?
(For the latter, instead of the loop have you tried)
message.channel.send(json.guild.members)
Otherwise another thing to consider would be utilizing the loop to generate an array of URLs that you could resolve together with a Promise.all. Wondering if the undesired output is a result of asynchronous behavior. Here's the api docs for reference:
https://javascript.info/promise-api
Hope that works, friend!
I'm trying out some dynamic options for storing environment variables throughout a distributed app. I'm struggling with combining a simple Socket.io with chat page with connection based on a URL from a config.json file. I thought it would be clever on page load to fetch the json file and feed the URL to the socket object. However, I keep running into a chicken and egg issue with the async processing. With what I have right now it starts the connection with the startSocket() function but I would like to have the socket variable available for other functions such as the event listener for the message form. Is there a better way to handle the Socket object get me out of this mess? Obviously new to web dev.
const messageContainer = document.getElementById('message-container')
const messageForm = document.getElementById('send-container')
const messageInput = document.getElementById('message-input')
const name = document.getElementById('chatUserField').getAttribute('data-chatUsername');
fetch('./assets/config.json')
.then(results => results.json())
.then(data => {
startSocket(JSON.stringify(data.SocketIO_URL).replace(/['"]+/g, ''))
})
.catch(err=>console.log(err))
function startSocket(url) {
const socket = io(url)
appendMessage('you joined')
socket.emit('new-user', name)
socket.on('chat-message', data => {
appendMessage(`${data.name}: ${data.message}`)
})
socket.on('user-connected', name => {
appendMessage(`${name} connected`)
})
socket.on('user-disconnected', name => {
appendMessage(`${name} disconnected`)
})
}
messageForm.addEventListener('submit', e => {
e.preventDefault()
const message = messageInput.value
appendMessage(`You: ${message}`)
socket.emit('send-chat-message', message)
messageInput.value = ''
})
function appendMessage(message) {
const messageElement = document.createElement('div')
messageElement.innerText = message
messageContainer.append(messageElement)
}
Error:
ReferenceError: socket is not defined
There's a few things going on here. The reason why you're getting the error, I presume, is not because of the async question you describe—rather it's because in the event listener, you have the following line:
socket.emit('send-chat-message', message)
However, socket isn't defined here within that function. You defined it in the startSocket function but it's locally scoped, meaning that outside of the code in startSocket, you can't access it.
Generally you want to add event listeners and all the stuff that depends on the loaded config in the promise .then handler, since JavaScript will then only run that when your config file is loaded (which is what you want!).
So I would return the socket object from startSocket:
function startSocket(url) {
const socket = io(url)
appendMessage('you joined')
socket.emit('new-user', name)
socket.on('chat-message', data => {
appendMessage(`${data.name}: ${data.message}`)
})
socket.on('user-connected', name => {
appendMessage(`${name} connected`)
})
socket.on('user-disconnected', name => {
appendMessage(`${name} disconnected`)
})
return socket;
}
Then I would assign it to a variable in the .then handler.
fetch('./assets/config.json')
.then(results => results.json())
.then(data => {
// do all processing related to setting stuff up now here
const mySocket = startSocket(JSON.stringify(data.SocketIO_URL).replace(/['"]+/g, ''));
// now that we have the socket object, let's set up the event listeners
messageForm.addEventListener('submit', e => {
e.preventDefault()
const message = messageInput.value
appendMessage(`You: ${message}`)
mySocket.emit('send-chat-message', message)
messageInput.value = ''
})
})
I want to isolate some login inside of a child process. The idea is pretty simple:
I wait for some event inside of master process
send a message to child process
if child process are able to handle it, then receive a result
if child process fails then, log erros and fork a new process
The problem here: messaging. So, I wrote a prototype solution bellow:
const minion = fork('./minion')
const setupSend = (emmiter) => {
const pool = {}
emmiter.on('message', ({id, msg}) => {
pool[id](msg)
delete pool[id]
})
const send = (msg) => {
const id = getId()
const refObj = {}
const p = new Promise((resolve) => {
refObj.resolve = resolve
})
pool[id] = refObj.resolve
emmiter.send({id , msg})
return p
}
return send
}
const send = setupSend(minion)
send('message to reverse').then((result) => {
console.log(result)
})
and sample minion code:
process.on('message', ({id, msg}) => {
process.send({id, msg: msg.split("").reverse().join("")})
});
It works but it doesn't handle the errors and exit cases. probably I will manage to write all the required logic, but it feels like I am inventing a wheel.
So, is there an easier way to achieve this functionality?
*SOLVED
The problem was in how I was creating and responding to the observable created by the firebase callback.
I also had way too much stuff going on inside my firebase callbacks.
I ended up splitting it up a bit more, using the firebase promise structure: https://firebase.googleblog.com/2016/01/keeping-our-promises-and-callbacks_76.html
and creating an Observable.fromPromise for the firebase callback within what is now called firebaseAPI.checkForUser.
*
Working epic:
export const contactFormFirebaseSubmitEpic = (action$) =>
action$.ofType(START_CONTACT_FORM_FIREBASE_SUBMIT)
.flatMap((firebaseSubmitAction) => {
const values = firebaseSubmitAction.values;
const formattedEmail = firebaseAPI.getFormattedEmail(values);
const contactsRef = firebaseAPI.getContactsRef(formattedEmail);
return firebaseAPI.checkForUser(values, formattedEmail, contactsRef);
})
.flatMap((data) => concat(
of(firebaseAPI.recordUserAndUpdateDetails(data))
))
.flatMap((data) => concat(
of(firebaseAPI.setQuoteData(data))
))
.switchMap((x) => merge(
of(stopLoading()),
of(contactFormFirebaseSuccess())
));
// original question
Ok so, what I'm trying to achieve is to perform the first action (firebaseAPI.checkUserAndUpdate), then the next, then when both of them are done essentially discard what's there and send out two actions (contactFormFirebaseSuccess and stopLoading).
This all works fine except for one weird thing, the setQuoteData function always runs before the checkUser function. Does anyone know why this might be?
Also if there's a better way to lay this out I'd be very open to suggestions! Cheers. Also I've taken out quite a few variables and things that would make it even more complicated. Basically I just wanted to show that in each case I'm returning an observable from 'doing something with firebase'. But I don't think that's the problem as I have console logs in each of the firebase functions and the setQuoteData one just fires first and then executes the firebase stuff then when it's done the checkUserAndUpdate one runs.
export const contactFormFirebaseSubmitEpic = action$ =>
action$.ofType(START_CONTACT_FORM_FIREBASE_SUBMIT)
.flatMap((firebaseSubmitAction) => {
const values = firebaseSubmitAction.values;
return merge(
firebaseAPI.checkUserAndUpdate(values),
firebaseAPI.setQuoteData(values),
)
.takeLast(1)
.mergeMap((x) => {
return merge(
of(contactFormFirebaseSuccess()),
of(stopLoading()),
);
});
});
const firebaseAPI = {
checkUserAndUpdate: (values) => {
const checkUserAndUpdateDetails = firebaseRef.once('value', snapshot => {
const databaseValue = snapshot.val();
checkUserExistsAndUpdateDetails(databaseValue, values);
});
return Observable.from(checkUserAndUpdateDetails);
},
setQuoteData: (value) => {
const setQuote = setQuoteData(values);
return Observable.from(setQuote);
},
};
const stopLoading = () => ({ type: STOP_BUTTON_LOADING });
const contactFormFirebaseSuccess = () => ({ type: SUCCESS });
checkUserAndUpdate: (values, contactsRef) => {
const checkUser$ = Observable.from(contactsRef.once('value').then(
snapshot => {
const databaseValue = snapshot.val();
checkUserExistsAndUpdateDetails(
values,
databaseValue,
contactsRef,);
})
);
return checkUser$;
},
const checkUserExistsAndUpdateDetails = (
values,
databaseValue,
contactsRef,
) => {
if (databaseValue) { console.log('user exists'); }
else {
console.log('new user, writing to database');
contactsRef.set({
name: values.name,
email: values.email,
phone: values.phone,
});
}
};
The problem is that merge does not maintain the order of the streams that you subscribe to, it simply emits events from any of the source streams regardless of what order they emit.
If you need to maintain order you should use concat instead of merge
i.e.
const values = firebaseSubmitAction.values;
return concat(
firebaseAPI.checkUserAndUpdate(values),
firebaseAPI.setQuoteData(values),
)
Side note, I don't know why you are using the of operator there, you already have Observables returned from your API so you can just pass those to merge or concat in this case.