How to find threadID in Realtime Database chat query - node.js

I'm working with Firebase Realtime Database and am a fairly new developer. I'm working on a chat application and need to detect which thread to put a message in that comes to a firebase function. The function gets the fromID (sender) and the userID (receiver) and needs to detect which thread belongs to the users.
Issue: The problem is that it successfully detects the thread the first time the message comes in, but then continues afterward - creating a new thread as if the relationship doesn't exist.
Structure:
users
- uid
- threads
- $threadKey
threadMembers
- $threadKey
- members
- $fromID_$userID
Code:
const db2 = admin.database();
db2.ref('users/' + userId + '/threads').once('value').then(snapshot => {
if (!snapshot.exists()) {
console.log("User does not have any threads")
return functions.logger.log('User doesnt have a Thread.');
}
snapshot.forEach((doc) => {
console.log("SNAP : " + doc.key)
checkThreads(doc.key)
})
return functions.logger.log('Finished checking user/uid/threads');
})
function checkThreads(threadId){
const threadRef = db2.ref('threadMembers/' + threadId);
threadRef.once('value', (snapshot) => {
const members = snapshot.child("members").val()
console.log("Members Val: " + members)
if(members == `${fromId}_${userId}` || members == `${userId}_${fromId}`){ //ISSUES WITH REPEATING
console.log("Both users are in the thread")
console.log("Key for thread: " + snapshot.key)
setThread(snapshot.key)
return functions.logger.log('Found membership and set thread');
} else {
const newThreadKey = db2.ref('threads').push().key;
const userThreadRef = db2.ref('users');
userThreadRef.child(fromId + '/threads/' + newThreadKey).set({
timestamp: DBTimestamp.TIMESTAMP
});
userThreadRef.child(userId + '/threads/' + newThreadKey).set({
timestamp: DBTimestamp.TIMESTAMP
});
const threadMembersRef = db2.ref('threadMembers');
threadMembersRef.child(newThreadKey + '/' + userId).set(true);
threadMembersRef.child(newThreadKey + '/' + fromId).set(true);
threadMembersRef.child(newThreadKey + '/members').set(userId + "_" + fromId);
setThread(newThreadKey)
return functions.logger.log('Created membership and set thread');
}
})
}

Related

How to make a firebase function (using on create) return an error that I can catch from my react native app

I have created a Firebase function for my shopping app, where the function is triggered when an order is created and then it checks the quantity of each product in the order and update product quantity in the database. I need this to keep track of how many items is left of each product.
However, in case one of the products in the order has more quantity than what left (the quantity of the product in the database), I need a way for the function to return an error I can catch from my react native app so I can inform the user that the quantity he asked for is not available. I also need the function to stop the creating of the order doc in the database.
Here's the firebase function I wrote:
exports.countOrderProductChange = functions.firestore.document("/orders/{id}")
.onCreate((change, context) => {
const data = change.data();
const itemsList = data["itemsList"];
let error = "";
const newProductsSizes = {};
for (const item of itemsList) {
db.collection("products").doc(item.product.id).get().then((doc) => {
const product = doc.data();
let sizes = [];
if (item.product.id in newProductsSizes) {
sizes = newProductsSizes[item.product.id];
} else {
sizes = product.sizes;
}
const remaingSize = sizes.find((size) => (size.name == item.size));
const remaingSizeQty = remaingSize.qty;
if (remaingSizeQty < item.qty) {
if (remaingSizeQty == 0) {
error = "Sorry there's no more (" + item.size +
") size of the product: " + item.product.name;
} else {
error = "Sorry there's only "+ remaingSizeQty +
" of (" + item.size +
") size of the product: " + item.product.name;
}
functions.logger.log(error);
return error;
} else {
const sizeIndex = sizes.findIndex((obj) => (obj.name == item.size));
const newQty = remaingSizeQty - item.qty;
const newSizes = sizes;
newSizes[sizeIndex].qty = newQty;
newProductsSizes[item.product.id] = newSizes;
}
});
}
for (const productId in Object.keys(newProductsSizes)) {
if (Object.prototype.isPrototypeOf.call(newProductsSizes, productId)) {
db.collection("products").doc(productId).update({
sizes: newProductsSizes[productId],
});
}
}
});
As Doug Stevenson commented, your application is not able to directly run the onCreate function, as this type of function is a background function. As shown in the third point, these functions are called from the backend:
When the event provider generates an event that matches the function's conditions, the code is invoked.
You can check this related post for more reference, which also mentions listening to a document used to hold the status of the operation.
Another alternative would be to use a callable function. These functions allow you to handle errors on the client and are called directly within your app.

Firebase Cloud Functions - multiple returns in sequnce

I am new to Firebase cloud functions. I would like to have a function in index.js that trigger when a node in database is created - 1. it will take the params of the context 2. and find a value of a node, 3. get value from another database, 4. adding that value to 3 diff places.
exports.runningNumber = functions.database.ref("Main/Enemy/{enem_Id}/{event_Id}"}
.onCreate((snapshot, context) => {
var enemid = context.params.enem_Id;
var eventid = context.params.event_Id;
return admin.database().ref("Main/Enemy/" + enemid + "/SequenceNumber").once('value', (snapshot) => {
var newSeqNum = snapshot.val() + 1;
//then, get var userid through database().ref("Main/Enemy/" + enemid + "/" + eventid + "/user_Id")
//and then, use newSeqNum to:
//1. Replacing "Main/Enemy/" + enemid + "/SequenceNumber"
//2. Adding as string into "Main/Enemy/" + enemid + "/" + eventid + "/SeqNum"
//3. Adding as string into "Main/Users/" + userid + "/" + eventid + "/SeqNum"
});
});
Hope somebody can help on how to do this...

How to avoid having “quota exceeded for 'ReadGroup'” error in google api

I've created a bot which goes in Google Spreadsheet getting some datas before sending them by DM to 50 guild members into Discord.
However, due to high requests of datas, I've got an error message saying that I've exceeded the quota for group 'ReadGroup' and limit 'USER-100s'.
To avoid getting this error, I've created a buffer function however it still doesn't work, does anyone know how to avoid getting this limit error?
Here is the main code which is launched when I type a specific commande in Discord :
const client1 = new google.auth.JWT(keys.client_email, null, keys.private_key, ['https://www.googleapis.com/auth/spreadsheets']);
const client2 = new discord.Client();
.
.
.
let valeur1 = await liste(client1);
await sleep(100000);
console.log("End of first buffering time (100s)");
for (i = 0; i < valeur1.length; i++){
if (valeur1[i] != undefined){
try{
let valeur2 = await envoi(client1, valeur1[i]);
const user = client2.users.get(String(valeur1[i])) || await client2.fetchUser(String(valeur1[i]));
console.log("Ready to send to : " + user.id);
await user.send("The character you need to improve is : " + valeur2[0] + "\n 1. " + valeur2[1] + " = " + valeur2[2] + " >>> " + valeur2[3] + "\n 2. " + valeur2[4] + " = " + valeur2[5] + " >>> " + valeur2[6] + "\n 3. " + valeur2[7] + " = " + valeur2[8] + " >>> " + valeur2[9]);
console.log("Message sent for : " + user.id);
}
catch(err){
console.error(err);
console.log("Error detected for : " + valeur1[i]);
break;
}
}
}
Here is the first function called ("liste") which return the list of the 50 members id :
async function liste(client){
const gsapi = google.sheets({version:'v4',auth: client});
let data1 = new Array();
for (i = 0; i < 50; i++) {
const opt1 = {spreadsheetId: 'XXXXXX', range: 'Serveur!C' + (3+i)};
let data2 = await gsapi.spreadsheets.values.get(opt1);
data1.push(data2.data.values);
}
return data1;
}
And here is the second function called ("envoi") which is supposed to send the DM to the 50 different members of the guild :
async function envoi(client, id){
const gsapi = google.sheets({version:'v4',auth: client});
for (i = 0; i < 50; i++){
const opt1 = {spreadsheetId: 'XXXXXX', range: 'Discord!A' + (3+i)};
let data1 = await gsapi.spreadsheets.values.get(opt1);
if (parseInt(id) === parseInt(data1.data.values)){
const opt2 = {spreadsheetId: 'XXXXXX', range: 'Discord!C' + (3+i)};
let data2 = await gsapi.spreadsheets.values.get(opt2);
const opt3 = {spreadsheetId: 'XXXXXX', range: 'Discord!D' + (3+i)};
let data3 = await gsapi.spreadsheets.values.get(opt3);
.
.
.
const opt10 = {spreadsheetId: 'XXXXXX', range: 'Discord!K' + (3+i)};
let data10 = await gsapi.spreadsheets.values.get(opt10);
const opt11 = {spreadsheetId: 'XXXXXX', range: 'Discord!L' + (3+i)};
let data11 = await gsapi.spreadsheets.values.get(opt11);
var stats = [data2.data.values,data3.data.values,data4.data.values,data5.data.values,data6.data.values,data7.data.values,data8.data.values,data9.data.values,data10.data.values,data11.data.values];
await sleep(10000);
console.log("Extraction done for " + parseInt(id));
return stats;
}
}
console.log("Member not found");
return "erreur";
}
As a result, I would like to get all the members to get their DM. However after the 18th member, an error appear, even though I put some buffering time.
In the console.log, I get :
End of first buffering time (100s)
Extraction done for 408575708424699900
Ready to send to : 408575708424699925
Message sent for : 408575708424699925
.
.
.
Extraction done for 438420776652374000
Ready to send to : 438420776652374036
Message sent for : 438420776652374036
Error: Quota exceeded for quota group 'ReadGroup' and limit 'USER-100s' of service 'sheets.googleapis.com'
.
.
.
Error detected for : 493854774446391296
This is even more strange that the error concerns a member who already have received his DM (he is one the the first 10 members in the list)
Thanks to Tanaike, I updated my code using spreadsheets.values.batchGet() method. In that way instead of extraction values by values, I extracted a batch of values.
And then I made my formula. Now I don't have any issues anymore and even better, my script is way much quicker :)

Random number generator does not produce an expected answer Node JS

In a Node JS script, a formula is being used to generate a random number between two values. Here is the function randomIntInc:
function randomIntInc(low, high) {
return Math.floor(Math.random() * (high - low + 1) + low);
}
The function is called with two values as low and high:
let randomNumber = randomIntInc(requestedStart, requestedEnd);
console.log(requestedStart) before executing the above line produces 542 (expected)
console.log(requestedEnd) before executing the above line produces 592 (expected)
Now, running the above line and logging the output produces any value, generally within 6 and 50.
What is happening? Logging before executing the random number shows the correct ranges, but the outputted number just does not fall within the given range.
Here's the full code (related code at 113) (please ignore bad practice code that's unrelated / memes or easter eggs):
// Copyright (C) 2018, Julian Lachniet, Jacob Wysko
// Dependencies
var Discord = require('discord.io'),
logger = require('winston'),
auth = require('../auth.json'),
rn = require('random-number'),
http = require('http'),
request = require('request');
// Authorize connection
var bot = new Discord.Client({
token: auth.token,
autorun: true
});
// GOOGLE SHEETS API
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets.readonly'];
const TOKEN_PATH = 'credentials.json';
// If bot disconnects, try to reconnect
bot.on('disconnect', function(msg, code) {
if (code === 0) return console.error(msg);
bot.connect();
});
// Returns the current timestamp
function timeStamp() {
return "[" + new Date().toString().split(" G")[0] + "]";
}
// Returns a random integer
function randomIntInc(low, high) {
return Math.floor(Math.random() * (high - low + 1) + low);
}
// When a message is recieved
bot.on('message', function(user, userID, channelID, message, evt) {
let valid = false;
// Log what the user sent (verifies it wasn't sent by the bot)
if (user !== 'teacher-quotes-bot') {
console.log(timeStamp() + " " + user + ": " + message);
}
// Sends a message with text "message", logs it
function sendMessage(message) {
bot.sendMessage({
to: channelID,
message: message
});
console.log(timeStamp() + " Bot: " + message);
valid = true;
}
// Sends a message based on a list of commands
function infoMessage(commands, response) {
if (commands.includes(input)) {
sendMessage(response);
}
validCommands.push(commands);
}
// If the message began with a '!' (e.g., !cmd)
if (message.substring(0, 1) === '!') {
var args = message.substring(1).split(' ');
var input = args[0];
var validCommands = [];
// Commands
infoMessage(['accuracy', 'disclaimer', 'terms'], '<#440534402171404289>');
infoMessage(['android', 'app'], 'https://play.google.com/store/apps/details?id=com.jacobwysko.teacherquotes');
infoMessage(['format', 'formats', 'formatting'], '"Quote text." - Teacher, MM/DD/YYYY');
infoMessage(['code', 'git', 'github', 'roadmap', 'trello', 'source', 'sourcecode'], 'https://github.com/wyskoj/TeacherQuotesVersion3\nhttps://github.com/jlachniet/TeacherQuotesBot');
infoMessage(['faq', 'help', '?'], "You've been <#440504422028804107>ed.");
infoMessage(['ping', 'pong'], 'Pong!');
infoMessage(['rule', 'rules'], "You've been <#440500062011916298>ed");
infoMessage(['apple', 'ios', 'web', 'website'], 'http://jacobwysko.com/teacherquotes');
infoMessage(['cmd', 'cmds', 'command', 'commands'], 'Valid Commands:\n - accuracy\n - app\n - format\n - github\n - help\n - ping\n - rules\n - website');
// Easter eggs
infoMessage(['ivefallenandicantgetup'], 'Stay right there, ' + user + "! Don't worry, help is on the way!");
infoMessage(['goddammitimpregnant'], '...for the fifteenth damn time!');
infoMessage(['yeah'], 'boiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii');
infoMessage(['ohhi'], 'mark');
infoMessage(['poopity'], 'scoop');
switch (input) {
case 'fact':
case 'randomfact':
// Sends a random fact
request('https://spreadsheets.google.com/feeds/list/1ggvma51cj7ryPqfxGv47ZPLuP72keZmFbGE-KBHqbuU/4/public/values?alt=json', function(error, response, body) {
let boi = body;
let output = JSON.parse(boi);
let options = {
min: 0,
max: 11,
integer: true
};
let randomNumber = rn(options);
sendMessage(output["feed"]["entry"][randomNumber]["gsx$randomfact"]["$t"]);
});
break;
case 'randomquote':
// Sends a random quote
if (args.length !== 2) {
request('https://spreadsheets.google.com/feeds/list/1ggvma51cj7ryPqfxGv47ZPLuP72keZmFbGE-KBHqbuU/1/public/values?alt=json', function(error, response, body) {
let output = JSON.parse(body);
let options = {
min: 0,
max: output["feed"]["entry"].length,
integer: true
};
let randomNumber = rn(options);
sendMessage(('"' + output["feed"]["entry"][randomNumber]["gsx$quote"]["$t"] + '" - ' + output["feed"]["entry"][randomNumber]["gsx$teacherquoted"]["$t"] + ", " + output["feed"]["entry"][randomNumber]["gsx$date"]["$t"]))
});
} else {
console.log("SPECIFICS TEACHER");
let requestedTeacher = args[1];
request('https://spreadsheets.google.com/feeds/list/1ggvma51cj7ryPqfxGv47ZPLuP72keZmFbGE-KBHqbuU/1/public/values?alt=json', function (error, response, body) {
let quotes = JSON.parse(body);
console.log(quotes);
request('https://spreadsheets.google.com/feeds/list/1ggvma51cj7ryPqfxGv47ZPLuP72keZmFbGE-KBHqbuU/2/public/values?alt=json', function (error, response, body){
let ranges = JSON.parse(body);
console.log(ranges);
let teachers = [];
for (i = 0; i < ranges["feed"]["entry"].length; i++){
teachers.push(ranges["feed"]["entry"][i]["gsx$teacher"]["$t"]);
}
let teacherStart = [];
for (i = 0; i < ranges["feed"]["entry"].length; i++){
teacherStart.push(ranges["feed"]["entry"][i]["gsx$rangestart"]["$t"]);
}
let teacherEnd = [];
for (i = 0; i < ranges["feed"]["entry"].length; i++){
teacherEnd.push(ranges["feed"]["entry"][i]["gsx$rangeend"]["$t"]);
}
let requestedTeacherId = teachers.indexOf(requestedTeacher);
let requestedStart = teacherStart[requestedTeacherId];
let requestedEnd = teacherEnd[requestedTeacherId];
console.log(requestedStart);
console.log(requestedEnd);
let randomNumber = randomIntInc(requestedStart, requestedEnd);
console.log(requestedTeacher);
console.log(randomNumber);
});
});
}
break;
}
if (!valid && !['fact', 'randomfact', 'randomquote'].includes(input)) {
sendMessage('Invalid command. Type "!command" for a list of commands.');
}
} else
if (user !== 'teacher-quotes-bot' && channelID === '465330717782441986') {
let validQuoteRegex = /".+"\s-\s\w+,\s\d{2}\/\d{2}\/\d{4}/;
if (validQuoteRegex.test(message)) { // If the message sent was a valid quote
let justMessage = message.split('"')[1];
let justTeacher = message.split('"')[2].substring(3, message.split('"')[2].length - 12);
let justDate = message.substring(message.length - 10, message.length);
if (justTeacher.indexOf("(") > -1) {
justTeacher = justTeacher.substring(0, justTeacher.indexOf("("));
}
sendMessage("Message: " + justMessage + "\nTeacher: " + justTeacher + "\nDate: " + justDate);
} else {
sendMessage("Invalid Quote");
}
}
});
It appears the returned JSON is storing the values assigned to requestedStart and requestedEnd as strings rather than numbers. If that is the case, you will need to convert them to numbers before performing the arithmetic for the random number range.
function randomIntInc(low, high) {
console.log({
low,
high,
'(high - low) + 1': (high - low) + 1,
'(high - low + 1) + low': (high - low + 1) + low
});
return Math.floor(Math.random() * (high - low + 1) + low);
}
console.log({
strings: randomIntInc('10', '20'),
numbers: randomIntInc(10, 20)
});
Stepping through the string invocation, '10' and '20', results in the following operations:
('20' - '10' + 1): the order of operations will cause the subtraction operations to be done first which will automatically convert both strings to numbers and result in 10. The final addition will simply add the two number and produce 11.
The key part is the last addition which attempts to add a number with a string. This will cause the number, '11', to be converted to a string and then concatenated with the string, '10', which results in '1110'. This can be confusing unless you remember that the addition symbol is also used for string concatenation.
Finally the multiplication uses the random number times a string value of '1110' which results in a value of 0 - 11.1.

Axios get request timeout gets swallowed (Promise)

I have been working on this data gathering module, that is supposed to get data from different bitcoin markets and standardise all the received data so it can be inserted into a mongodb database for later use. (The module is written in node 4.3.x)
The problem I have is that I need consistency in how the data is represented in the database. So whenever a request times out, instead of catching the get request and log an error, I want to resolve a '0'.
Additionally the received data contains trades that need to be cut in order. This needs to happen so that the trades can be cut properly, so data is not written twice.
For that I have implemented two queues:
1: TimestampQueue - holds timestamps. The timestamp in [0] is the next expected response
2: objectQueue - holds received responses
=> as soon as the object in objectQueue[0] equals the timestamp in timestampQueue[0] => do data manipulation and insert into database.
The problem lies that the axios.get request that should catch a timeout doesn't do that consistently.
It happens after random timeframes, but on average the queue gets stuck after 2hrs.
To make things clearer here some important code snippets:
httpclient making the axios request:
get(url) {
return this.instance.get(url) //instance just defined a timeout. Nothing special
.then(response => {
return response.data;
})
.catch(error => {
throw error; //THIS SEEMINGLY DOESN'T GET EXECUTED IN THE DESCRIBED CASE
});
}
Now the marketHandler that resolves the request:
getMethodFromMarket(method, market, timestamp){
if(this.markets[market]){
if(this.markets[market].methods[method]) {
var url = this.markets[market].methods[method];
let result = {};
result[method] = {};
result[method][market] = {};
return this.client.get(url)
.then(data => {
result[method][market] = data;
log.debug("Successfully received " + method + " for " + market + " : " + timestamp);
return result;
})
.catch(err => {
result[method][market] = 0;
log.error(new Error("Failed to get " + method + " for " + market + ": " + timestamp));
log.error(err);
return result;
});
} else{
return Promise.reject(new Error(method + " not available for " + market));
}
} else {
return Promise.reject(new Error("Market not specified in config"));
}
}
The code that makes the requests for all defined markets (for one method) and joins them in one object:
//returns promise to get *method* from all markets specified in
//config.json
getAllMarkets(method, timestamp){
let getMarketsPromises = [];
let result = {};
result[method] = {};
Object.keys(this.markets).forEach(market => {
result[method][market] = {};
getMarketsPromises.push(this.getMethodFromMarket(method, market, timestamp));
});
return Promise.all(getMarketsPromises)
.then(results => {
for(let i = 0; i < results.length; i++){
let market = Object.keys(results[i][method])[0];
result[method][market] = results[i][method][market];
}
log.debug("Got all markets for " + method + " for " + timestamp);
return result;
})
}
The code that makes the requests for all methods and markets and joins them in the final object that gets manipulated from a different module and inserted into the database:
//returns promise to get trades and depths from markets specified in
//config.json
getEverything(timestamp){
let getMethodPromises = [];
let result = {timestamp};
this.methods.forEach(method => {
result[method] = {};
getMethodPromises.push(this.getAllMarkets(method, timestamp))
});
return Promise.all(getMethodPromises)
.then(results =>{
for(let i = 0; i < results.length; i++){
let method = Object.keys(results[i])[0];
result[method] = results[i][method];
}
log.debug("Got everything for " + timestamp);
return result;
})
}
I have tested the whole process without any data manipulation. Only those functions and inserting it into the database.
The implementation of the 2 queues:
//handles the incoming responses from markets and sorts
//them according to their timestamp
queueResponse(marketInfo){
this.marketInfoQueue.push(marketInfo);
this.marketInfoQueue.sort(function(a, b){
return a.timestamp - b.timestamp;
})
}
//returns queued Responses in order of timestamps.
getQueuedResponses(){
var i = 0;
var results = [];
log.debug("TimestampQueue: "+ this.timestampQueue[0] + " | objectQueue: " + this.marketInfoQueue[0].timestamp);
while(this.marketInfoQueue[i] && this.timestampQueue[i] == this.marketInfoQueue[i].timestamp){
results.push(this.marketInfoQueue.shift());
this.timestampQueue.shift();
i++;
}
return results;
}
//pushes new timestamp into timestampQueue to keep
//incoming responses in order
queueTimestamp(timestamp){
this.timestampQueue.push(timestamp);
}
I have been trying to fix this problem for more than 3 weeks now, and I am absolutely clueless.
TLDR: Axios get request does not resolve or reject. Even though a timeout of 5000ms is defined in the instance used in the httpClient module.

Resources