I have a project with Firebase. I want to have access to the attribute "text" of my database with a cloud function as soon as there is a recent branch added to my database.
I am not get used to using their database.
My code in NodeJs is bellow :
exports.messageAnswer = functions.database.ref('/messages')
.onWrite(event => {
const snapshot = event.data;
const val = snapshot.val();
var textMsg = val.text;
var regex = "(bot)";
//var database = firebase.database();
if(!textMsg.match(regex) && textMsg.length > 0){
var id = new Date().getTime().toString();
var object = {
text : "I am the Cloud bot #" + id,
};
admin.database().ref('/messages').push(object);
}
return 0;
});
My problem is the fact that there is a "PUSH ID" : I don't know how to contourne this unique string to get the value of the attribute "text".
You can use wildcards to get the text attribute some something like this:
exports.messageAnswer = functions.database.ref('/messages/{pushid}')
.onWrite(event => {
for more info check this: https://firebase.google.com/docs/database/extend-with-functions
You can specify a path component as a wildcard by surrounding it with curly brackets; ref('foo/{bar}') matches any child of /foo. The values of these wildcard path components are available within the event.params object of your function. In this example, the value is available as event.params.bar
Your current code is being triggered whenever any data under /messages changes. From your description it seems you only want to be triggered when a new message is added. You do that by using onCreate instead of onWrite. In addition you'll want to listen for changes to a specific message, instead of to the entire /messages node:
exports.messageAnswer = functions.database.ref('/messages/{messageId}')
.onCreate(event => {
Also see Doug's blog post on these triggers: https://firebase.googleblog.com/2017/07/cloud-functions-realtime-database.html
Related
I am following a tutorial and I understood everything up until everything beyond where I declared the let variable.
function submitMessage(event) {
event.preventDefault();
const email = document.getElementById("email").value;
const fullName = document.getElementById("fullName").value;
const feedbackType = document.getElementById("feedbackType").value;
const comment = document.getElementById("comment").value;
const messageObject = {
email,
fullName,
feedbackType,
comment
};
let currentMessages = [];
if (window.sessionStorage.getItem("messages")) {
currentMessages =
JSON.parse(
window.sessionStorage.getItem("messages")
);
}
currentMessages.push(messageObject);
window.sessionStorage.setItem(
"messages",
JSON.stringify(currentMessages)
);
}
You're setting the "messages" key for the session storage here:
window.sessionStorage.setItem(
"messages", // arbitrary key name
JSON.stringify(currentMessages) // value to store for this key
);
let currentMessage is an empty array that will hold messageObject variable expressed few row before.
After currentMessage there is a step that setup a session store that we going to call "messages".
The window.sessionStorage function is used to save some data inside the browser, in our case currentMessage. In this way if your refreshed the browser page you will able to get the last data save in window.sessionStorage.
So in the first step this function try to get messages object from the session storage that we have e called messages.
Then, one fetched it will push the currentMessage inside it with setItem, so after you reload the browser you will be able to retrieve the array passing through the session storage getItem and to get this value it need to search inside some key and the key is messages, in other word the key in the session storage that can hold our array.
I have a Google spreadsheet with links to questionnaires. I wanted to know how to get the questions from each of the questionnaires.
I guess I have to do: at best, use the script editor and iterate on the lines, and at worst, do webscraping.
const puppeteer = require('puppeteer');
function appendString() {
var range = SpreadsheetApp.getActiveSheet().getActiveRange();
var numRows = range.getNumRows();
var numCols = 0;
for (var i = 1; i <= numRows; i++) {
for (var j = 1; j <= numCols; j++) {
var currentValue = range.getCell(i,j).getValue();
await page.goto(currentValue);
const pollFrame = page.frames().find() # From there I have some difficulties
}
}
}
But I get the following error:
SyntaxError: await is only valid in async function (ligne 10, fichier "Code.gs")
Not to mention the async problem or the buttonthat I still have to click, the selection looks like this:
<div class="freebirdFormviewerViewItemsItemItemTitle exportItemTitle freebirdCustomFont" id="i1" role="heading" aria-level="3" aria-describedby="i.desc.310938276">How often did you fly before the Covid-19 epidemic? </div>
But the IDs don't follow a logical numerical order, so I don't know how to extract them automatically.
Then I don't know how to do it. I wonder if it's simpler because they're products from the same supplier.
Here is the equivalent in csv format:
https://docs.google.com/forms/d/e/1FAIpQLSfzocEm6IEDKVzVGOlg8ijysWZyAvQur0NheJb_I_xozgKusA/viewform?usp=sf_link
https://docs.google.com/forms/d/e/1FAIpQLScrm0ZTrvlONf5MX37N93H_FajNzfbNy9ZtitX-Vq9PPuLPHA/viewform?usp=sf_link
https://docs.google.com/forms/d/e/1FAIpQLSeolFSh3OyS_XpX1lRIJP-8CH8WG0X0hL98SM9d85LqC22Bow/viewform?usp=sf_link
Update
So I tried the anwer kindly posted by Neven Subotic's:
// this array will store forms and their questions
let formAndQuestions = [];
let formIds = ["https://docs.google.com/forms/d/e/1FAIpQLSfzocEm6IEDKVzVGOlg8ijysWZyAvQur0NheJb_I_xozgKusA/viewform?usp=sf_link",
"https://docs.google.com/forms/d/e/1FAIpQLScrm0ZTrvlONf5MX37N93H_FajNzfbNy9ZtitX-Vq9PPuLPHA/viewform?usp=sf_link",
"https://docs.google.com/forms/d/e/1FAIpQLSeolFSh3OyS_XpX1lRIJP-8CH8WG0X0hL98SM9d85LqC22Bow/viewform?usp=sf_link"]
formIds.forEach( formId => {
const form = FormApp.openById( formId );
// lets get the name
const formName = form.getTitle();
// first we get all items
const allItemsInThisForm = form.getItems();
// then we get filter out anything that is not a questions
const allQuestionsInThisForm = allItemsInThisForm.filter( item => {
return isThisItemAQuestion( item )
});
// now we store them in our object
formAndQuestions.push( {
formId: formId,
formName: formName,
questions: allQuestionsInThisForm
})
});
// this function is used to only get the itemTypes you want
// see reference for more information
function isThisItemAQuestion( item ){
const itemType = item.getType();
const validQuestionItemTypes = [ FormApp.ItemType.TEXT, "add others here" ]
let isValid = false;
validQuestionItemsTypes.forEach( validItemType => {
if( itemType == validItemType ) {
isValid = true;
}
});
return isValid
}
Unfortunately I obtain the following error message with the following details Exception: No item with the given ID could be found, or you do not have permission to access it. (line 9, "const form = FormApp.openById( formId );"). I don't understand. As you can see in the gif, I can open these links, so I should have the permission to access them isn't it?
I also tried Ruben's ideas with:
// this array will store forms and their questions
let formAndQuestions = [];
let formIds = ["https://docs.google.com/forms/d/e/1FAIpQLSfzocEm6IEDKVzVGOlg8ijysWZyAvQur0NheJb_I_xozgKusA/viewform?usp=sf_link"]//,
//"https://docs.google.com/forms/d/e/1FAIpQLScrm0ZTrvlONf5MX37N93H_FajNzfbNy9ZtitX-Vq9PPuLPHA/viewform?usp=sf_link",
//"https://docs.google.com/forms/d/e/1FAIpQLSeolFSh3OyS_XpX1lRIJP-8CH8WG0X0hL98SM9d85LqC22Bow/viewform?usp=sf_link"]
function scrapeForms(){
formIds.forEach( formId => {
// The code below logs the HTML code of the Google home page.
var response = UrlFetchApp.fetch(formId);
results = response.getElementsByClassName("freebirdFormviewerViewItemsItemItemTitleContainer");
Logger.log(results.getContentText())
});
}
But got back:
TypeError: response.getElementsByClassName is not a function (ligne 13, fichier "Code")
According to What is this Javascript "require"? require is not part of the standard JavaScript an AFAIK it's not supported by Google Apps Script.
By the other hand, the error message can't be easily solved as Google Apps Script Chrome V8 engine doesn't support async functions. Related Is google apps script synchronous?
If you will be using Google Apps Script, and you are the form owner or a form editor, instead of trying to web scraping a Google Form use the Forms Service of Google Apps Script. For this you will need the form ../edit URLs instead of the ../viewform URLs. On the official docs there is a quickstart that might help you https://developers.google.com/apps-script/quickstart/forms.
You could use openByUrl to "open" a form. It will not be actually opened in your web browser, it will be opened on the server side. Then you could use getItems to get all the questions, sections, images, videos, etc.
If you aren't the form owner or a form editor then you should use UrlFetchApp service and somehow parse the web page source code of each form based on the position of the questions. Related question: Google Sheets: How to import the following data?
Also, if the form has several sections you should do a post request to emulate clicking on the next button in order to get the second and following sections. There are more "also if the form has..." but I will stop here as the main part of question was already answered, I think.
You first want to get all the forms, so place those in an array:
const formIds = ["someId", "anotherId", "andSoOn"]
Then, lets use the FormApp to get the form and all items. Items can be of different types, see documentation.
// this array will store forms and their questions
let formAndQuestions = [];
formIds.forEach( formId => {
const form = FormApp.openById( formId );
// lets get the name
const formName = form.getTitle();
// first we get all items
const allItemsInThisForm = form.getItems();
// then we get filter out anything that is not a questions
const allQuestionsInThisForm = allItemsInThisForm.filter( item => {
return isThisItemAQuestion( item )
});
// now we store them in our object
formAndQuestions.push( {
formId: formId,
formName: formName,
questions: allQuestionsInThisForm
}
});
// this function is used to only get the itemTypes you want
// see reference for more information
function isThisItemAQuestion( item ){
const itemType = item.getType();
const validQuestionItemTypes = [ FormApp.ItemType.TEXT, "add others here" ]
let isValid = false;
validQuestionItemsTypes.forEach( validItemType => {
if( itemType == validItemType ) {
isValid = true;
}
});
return isValid
}
Then you can initially log out the results and see what it looks like:
Logger.log( formAndQuestions )
Item Types
exports.editData = functions.database.ref('/AllData/hello/A').onWrite((change, context) => {
const after = change.after;
if (after.exists()) {
const data = after.val();
var value = data;
// set of data to multiply by turns ratio
var actualEIn = (value.ein)*200;
console.log('Data Edited');
}
return admin.database().ref('/editedData/hello/A').push({
ein: actualEIn,
});
});
Edit: made some edits to the code as suggested! However, when I deploy it there are literally no logs.
Change this:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((snapshot) => {
const data = snapshot.val();
if (data.exists()) {
into this:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((change,context) => {
const data = change.after.val();
if (data.exists()) {
more info here:
https://firebase.google.com/docs/functions/beta-v1-diff#realtime-database
exports.editData = functions.database.ref('/AllData/hello/A/{id}').onWrite((change, context) => {
const afterData = change.after;
if (afterData.exists()) {
console.log('hey');
const data = afterData.val();
// set of data to multiply by turns ratio
var actualEIn = (data.ein)*200;
}
return admin.database().ref('/editedData/hello/A').push({
ein: actualEIn,
});
});
Hi guys thank you for all your help! :) I managed to solve this by adding a /{id} at the back!
You've got two things wrong here.
First, newer versions of the firebase-functions SDK since version 1.0 deliver a Change object to onWrite handlers instead of a snapshot, as it appears you are expecting. The Change object has properties for before and after with DataSnapshot objects of the contents of the database before and after the change that triggered the function. Please read the documentation for database triggers to get all the information.
Second, exists() is a method on DataSnapshot, but you're using it on the raw JavaScript object value of the contents of the database the location of change. JavaScript objects coming from val() will not have any methods to call.
You should probably update your code to:
Use the latest version of the firebase-functions module
Alter your function to accept the Change object instead of a snapshot
Use the exists() method on a snapshot in the change, rather than a raw JavaScript object.
Starter code:
exports.editValues = functions.database.ref('/AllData/hello/A').onWrite((change) => {
const after = change.after; // the DataSnapshot of the data after it was changed
if (after.exists()) {
const data = after.val() // the raw JavaScript value of the location
// use data here
}
})
I would like to be able to publish simultaneously in two directories of my Firebase database. I created a function for this, according to the example proposed in the "Update specific fields" section of the Firebase Javascript documentation:
function linkTwoUsers(user1, user2) {
// The two users are "connected".
var user1Data = {
userLink: user2
};
var user2Data = {
userLink: user1
};
var updates = {};
updates["/users/" + user1] = user1Data;
updates["/users/" + user2] = user2Data;
return database
.ref()
.update(updates)
.then(() => {
return res.status(200).end();
})
.catch(error => {
return res.status(500).send("Error: " + error.message);
});
}
The problem is that when I run the function, instead of uploading the directories, it replaces all the data present in it.
Here are the user directories before the function:
And then:
How do we make sure the data doesn't overwrite the others? Thank you for your help.
Try to narrow your path to just the property you are trying to update:
updates["/users/" + user1 + "/userLink/"] = user1;
updates["/users/" + user2 + "/userLink/"] = user2;
It seems as though you're creating an entirely new object when you set:
var userData = { someThing: stuff }
When you pass that in, it will override the original object. One way you might solve this (there might be a more efficient way) is to grab the objects from Firebase, add the new property and value to the object, then send the entire object back into Firebase.
In some javascript frameworks, you should be able to use the spread operator to set all of an object's props to another object like this:
var newObject = { ...originalObject }
newObject.userData = "something"
// then save newObject to firebase
I am trying to set some variables to send to pass into a function if a Firebase node is set to true. I am trying to use the .parent and .val() function to set a customer_id, based on the documentation here: https://firebase.google.com/docs/functions/database-events
exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
// Retrieve true/false value to verify whether card should be kept on file
const saveSource = event.data.val();
if (saveSource) {
let snap = event.data;
let customer_id = snap.ref.parent.child('customer_id').val();
console.log(customer_id);
// pass customer_id into function
}
I was expecting snap.ref.parent to reference /sources and .child('customer_id').val() to access the value from the customer_id key.
However, when I try to run this function I get the following error:
TypeError: snap.ref.parent.child(...).val is not a function
at exports.linkCardToSquareAccount.functions.database.ref.onWrite.event (/user_code/index.js:79:56)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:35:20
at process._tickDomainCallback (internal/process/next_tick.js:129:7)
How can I reference a node outside the scope of the original onWrite location?
You can't just call .val() on a database reference and expect to get the data at that location. You need to add a value listener in order to get new data.
Fortunately, this is fully supported inside of Cloud Functions:
exports.newCloudFunction = functions.database.ref('/user/{userId}/sources/saveSource').onWrite(event => {
// Retrieve true/false value to verify whether card should be kept on file
const saveSource = event.data.val();
if (saveSource) {
const customerIdRef = event.data.adminRef.parent.child('customer_id')
// attach a 'once' value listener to get the data at this location only once
// this returns a promise, so we know the function won't terminate before we have retrieved the customer_id
return customerIdRef.once('value').then(snap => {
const customer_id = snap.val();
console.log(customer_id);
// use customer_id here
});
}
});
You can learn more here.