I'm trying to create a metrics dashboard by running a nodejs application on a raspberry pi that will be attached to a large monitor.
The application queries a SQL server every hour on the hour. When I run it on my mac it works great, but when I run this on the raspberry, it seems to run once or twice, then stops. I have disabled screensavers on the raspberry but I'm not sure how to debug this. I'll include the full code since I'm not sure if it's the tick() function itself or something else...
"use strict";
var fs = require('fs');
var readline = require('readline');
var google = require('googleapis');
var googleAuth = require('google-auth-library');
var pg = require('pg');
const config = {
user: '<redacted>',
database: '<redacted>',
password: '<redacted>',
host: '<redacted>',
port: 5432,
max: 10,
idleTimeoutMillis: 5000,
};
const pool = new pg.Pool(config);
const spreadsheetId = '<this my internal sheet>'; // fulfillment kpis
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/sheets.googleapis.com-nodejs-quickstart.json
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
const TOKEN_DIR = (process.env.HOME || process.env.HOMEPATH ||
process.env.USERPROFILE) + '/.credentials/';
const TOKEN_PATH = TOKEN_DIR + 'olr-test.json';
// Load client secrets from a local file.
fs.readFile('secret.json', function processClientSecrets(err, content) {
if (err) {
console.log('Error loading client secret file: ' + err);
return;
}
// Authorize a client with the loaded credentials, then call the
// Google Sheets API.
authorize(JSON.parse(content), begin); // <---- LOOK HERE...WHERE IT REALLY BEGINS...I THINK
});
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
*
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
var clientSecret = credentials.installed.client_secret;
var clientId = credentials.installed.client_id;
var redirectUrl = credentials.installed.redirect_uris[0];
var auth = new googleAuth();
var oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
getNewToken(oauth2Client, callback);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
*
* #param {google.auth.OAuth2} oauth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback to call with the authorized
* client.
*/
function getNewToken(oauth2Client, callback) {
var authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('Enter the code from that page here: ', function (code) {
rl.close();
oauth2Client.getToken(code, function (err, token) {
if (err) {
console.log('Error while trying to retrieve access token', err);
return;
}
oauth2Client.credentials = token;
storeToken(token);
callback(oauth2Client);
});
});
}
/**
* Store token to disk be used in later program executions.
*
* #param {Object} token The token to store to disk.
*/
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code != 'EEXIST') {
throw err;
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to ' + TOKEN_PATH);
}
// Let's get the information out of xTuple now...
function begin(auth) {
// Promise function to return results from SQL query passed as an argument
function getResults(passedQuery) {
return new Promise((resolve, reject) => {
pool.connect((err, client, done) => {
var sql = client.query(passedQuery);
sql.on('row', (row, result) => {
result.addRow(row);
});
sql.on('end', (result) => {
var rightNow = timeStamp();
// console.log('Query finished at %s', rightNow);
resolve(result.rows);
});
done(err);
if (err) {
return console.error('error running query', err);
}
});
});
pool.on('error', (err, client) => {
console.error('idle client error', err.message, err.stack);
});
}
function convertArray(sqlObj) {
return new Promise((resolve, reject) => {
var result = [];
if (sqlObj.length > 0) {
var order = Object.keys(sqlObj[0]);
var result = sqlObj.map((row) => {
return order.map((col) => {
return row[col];
});
});
}
result[0].push(timeStamp());
resolve(result);
});
}
function appendSheet (value) {
return new Promise((resolve, reject) => {
var sheets = google.sheets('v4');
var options = {
auth: auth,
spreadsheetId: spreadsheetId,
range: "Sales ABPH!A1:B",
valueInputOption: "USER_ENTERED",
resource: {
range: "Sales ABPH!A1:B",
majorDimension: "ROWS",
values: value,
}
}
// console.log(options);
sheets.spreadsheets.values.append(options, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
function fixFormats(id) {
return new Promise((resolve, reject) => {
var sheets = google.sheets('v4');
var options = {
auth: auth,
spreadsheetId: spreadsheetId,
resource: {
requests: [{
repeatCell: {
fields: "userEnteredFormat.numberFormat",
range: {
sheetId: id,
startColumnIndex: 0, // inclusive
endColumnIndex: 1, // exclusive...meaning this counts UP TO column 4 but NOT 5
},
cell: {
userEnteredFormat: {
numberFormat: {
type: "CURRENCY",
pattern: "\"$\"#,##0.00",
}
}
}
}
}],
}
}
sheets.spreadsheets.batchUpdate(options, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
function timeStamp() {
var now = new Date();
var date = [now.getMonth() + 1, now.getDate()];
var time = [now.getHours(),now.getMinutes(),now.getSeconds()];
var suffix = (time[0] < 12) ? "AM" : "PM";
time[0] = (time[0] < 12) ? time[0] : time[0] - 12;
time[0] = time[0] || 12;
for (var i = 1; i < 3; i++) {
if (time[i] < 10) {
time[i] = "0" + time[i];
}
}
return date.join("/") + " " + time.join(":") + " " + suffix;
}
function insertRow() {
return new Promise((resolve, reject) => {
var sheets = google.sheets('v4');
var options = {
auth: auth,
spreadsheetId: spreadsheetId,
resource: {
requests: [{
insertRange: {
range: {
sheetId: 1769404692,
startRowIndex: 1,
endRowIndex: 2,
startColumnIndex: 1,
endColumnIndex: 4
},
shiftDimension: "ROWS",
}
}],
}
}
sheets.spreadsheets.batchUpdate(options, (err, res) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
function update() {
getResults(`
select formatMoney(sum(qry.total)) as \"Booked\"
FROM cohead c
LEFT OUTER JOIN
(SELECT coitem_cohead_id, SUM(coitem_qtyord*coitem_price) as total
FROM coitem
WHERE coitem_status NOT IN (\'X\')
GROUP BY coitem_cohead_id) as qry
ON (c.cohead_id=qry.coitem_cohead_id)
WHERE (extract(DAY FROM c.cohead_orderdate) = extract(DAY from current_date) AND extract(MONTH from c.cohead_orderdate) = extract(MONTH from current_date) AND extract(YEAR from c.cohead_orderdate) = extract(YEAR from current_date))
`)
.then((sqlObj) => {
insertRow();
return convertArray(sqlObj);
})
.then((results) => {
console.log(results);
return appendSheet(results);
})
.then((fixMe) => {
fixFormats('1769404692');
})
.catch((error) => {
console.log('Something went wrong...\n%s', error);
});
tick();
}
function tick(force) {
var d = new Date();
var min = d.getMinutes();
var sec = d.getSeconds();
var hr = d.getHours();
if (force || (min == '0' && sec == '0') && (hr >= 7 || hr <= 19))
update();
else
setTimeout(update, (60 * (60 - min) + (60 - sec)) * 1000);
}
tick(true);
}
I'm running it with nodemon to monitor for changes in the script file and restart if found.
My hunch is that somehow the raspberry is going into a sort of "sleep" mode but still outputs video.
EDIT: I thought I found a solution here: https://raspberrypi.stackexchange.com/questions/4773/raspberry-pi-sleep-mode-how-to-avoid/56046#56046
I've turned off power_save on for wlan0 (which is what I'm using for my LAN connection) and then rebooted. Same problem, but checking the power_save option again looks like it was turned back on.
I've just tested turn it off, rebooting, and immediately checking again it's re-enabled.
EDIT #2: Confirming that if I set power_save off on wlan0 and do not reboot, it still does not run the loop at the appropriate timeout. I waited 30 minutes before restarting it and it ran fine but that's because of the tick() is forced the first time it runs.
Related
This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 10 months ago.
I am trying to make a chat application project with abusive text detection. I found code for the chat application online and want to add text detection using Perspective API. The API has several attributes for toxicity, threat etc. I am able to set the attributes inside the API function but I am unable to access them outside it.
Here is the relevant code:-
const sendMessage = asyncHandler(async (req, res) => {
const { content, chatId } = req.body;
let toxicity, insult, profanity, threat;
if (!content || !chatId) {
console.log("Invalid data passed into request");
return res.sendStatus(400);
}
let newMessage = {
sender: req.user._id,
content: content,
chat: chatId,
toxicity: toxicity,
insult: insult,
profanity: profanity,
threat: threat,
};
let inputText = newMessage.content;
// Perspective API
google
.discoverAPI(process.env.DISCOVERY_URL)
.then((client) => {
const analyzeRequest = {
comment: {
text: inputText,
},
requestedAttributes: {
TOXICITY: {},
INSULT: {},
PROFANITY: {},
THREAT: {},
},
};
client.comments.analyze(
{
key: process.env.API_KEY,
resource: analyzeRequest,
},
(err, response) => {
if (err) throw err;
// console.log(JSON.stringify(response.data, null, 2));
toxicity = (response.data.attributeScores.TOXICITY.summaryScore.value * 100).toFixed(2);
insult = (response.data.attributeScores.INSULT.summaryScore.value * 100).toFixed(2);
profanity = (response.data.attributeScores.PROFANITY.summaryScore.value * 100).toFixed(2);
threat = (response.data.attributeScores.THREAT.summaryScore.value * 100).toFixed(2);
newMessage.toxicity = toxicity;
newMessage.insult = insult;
newMessage.profanity = profanity;
newMessage.threat = threat;
console.log("1-" + newMessage.toxicity); // This returns the desired output
}
);
})
.catch((err) => {
throw err;
});
//
console.log("2-" + newMessage.toxicity); // This returns undefined
try {
let message = await Message.create(newMessage);
message = await message.populate("sender", "name profilePic");
message = await message.populate("chat");
message = await User.populate(message, {
path: "chat.users",
select: "name profilePic email",
});
await Chat.findByIdAndUpdate(req.body.chatId, {
latestMessage: message,
});
res.json(message);
} catch (error) {
res.status(400);
throw new Error(error.message);
}
});
I want newMessage to be updated after the API call. After coming across this post, I found that console.log("2-" + newMessage.toxicity) executes before console.log("1-" + newMessage.toxicity). I tried using callbacks and async/await but couldn't make it work.
The console.log("2-" + newMessage.toxicity) is outside the google.discoverAPI call so it execute instantly.
you can try something like this
const sendMessage = asyncHandler(async (req, res) => {
const { content, chatId } = req.body;
let toxicity, insult, profanity, threat;
if (!content || !chatId) {
console.log("Invalid data passed into request");
return res.sendStatus(400);
}
let newMessage = {
sender: req.user._id,
content: content,
chat: chatId,
toxicity: toxicity,
insult: insult,
profanity: profanity,
threat: threat,
};
let inputText = newMessage.content;
// Perspective API
const client = await google
.discoverAPI(process.env.DISCOVERY_URL)
const analyzeRequest = {
comment: {
text: inputText,
},
requestedAttributes: {
TOXICITY: {},
INSULT: {},
PROFANITY: {},
THREAT: {},
},
};
await new Promise((resolve, reject) => {
client.comments.analyze(
{
key: process.env.API_KEY,
resource: analyzeRequest,
},
(err, response) => {
if (err) {
reject(err)
}
// console.log(JSON.stringify(response.data, null, 2));
toxicity = (response.data.attributeScores.TOXICITY.summaryScore.value * 100).toFixed(2);
insult = (response.data.attributeScores.INSULT.summaryScore.value * 100).toFixed(2);
profanity = (response.data.attributeScores.PROFANITY.summaryScore.value * 100).toFixed(2);
threat = (response.data.attributeScores.THREAT.summaryScore.value * 100).toFixed(2);
newMessage.toxicity = toxicity;
newMessage.insult = insult;
newMessage.profanity = profanity;
newMessage.threat = threat;
console.log("1-" + newMessage.toxicity);
resolve()
}
);
})
.catch((err) => {
throw err;
});
//
console.log("2-" + newMessage.toxicity); // This returns undefined
try {
let message = await Message.create(newMessage);
message = await message.populate("sender", "name profilePic");
message = await message.populate("chat");
message = await User.populate(message, {
path: "chat.users",
select: "name profilePic email",
});
await Chat.findByIdAndUpdate(req.body.chatId, {
latestMessage: message,
});
res.json(message);
} catch (error) {
res.status(400);
throw new Error(error.message);
}
});
this is my code for the gmail api. my logic is that i want the get messages to run for every message in the callback of the list function. so my console shoudl be logging the info of every message id but when i run this code nothing is being logged. i know that the list function is working and the log shows the ids of every message in the array before the loop runs. However, I am not sure why the loop isnt working.
const fs = require('fs')
const readline = require('readline')
const { google } = require('googleapis')
const TOKEN_PATH = 'token.json'
// Load client secrets from a local file.
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err)
// Authorize a client with credentials, then call the Gmail API.
authorize(JSON.parse(content), listMessages)
})
/**
* Create an OAuth2 client with the given credentials, and then execute the
* given callback function.
* #param {Object} credentials The authorization client credentials.
* #param {function} callback The callback to call with the authorized client.
*/
function authorize(credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed
const oAuth2Client = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0],
)
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getNewToken(oAuth2Client, callback)
oAuth2Client.setCredentials(JSON.parse(token))
callback(oAuth2Client)
})
}
/**
* Get and store new token after prompting for user authorization, and then
* execute the given callback with the authorized OAuth2 client.
* #param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
* #param {getEventsCallback} callback The callback for the authorized client.
*/
function getNewToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
})
console.log('Authorize this app by visiting this url:', authUrl)
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
})
rl.question('Enter the code from that page here: ', (code) => {
rl.close()
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err)
oAuth2Client.setCredentials(token)
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err)
console.log('Token stored to', TOKEN_PATH)
})
callback(oAuth2Client)
})
})
}
/**
* Lists the labels in the user's account.
*
* #param {google.auth.OAuth2} auth An authorized OAuth2 client.
*/
function listLabels(auth) {
const gmail = google.gmail({ version: 'v1', auth })
gmail.users.labels.list(
{
userId: 'me',
},
(err, res) => {
if (err) return console.log('The API returned an error: ' + err)
const labels = res.data.labels
if (labels.length) {
console.log('Labels:')
labels.forEach((label) => {
console.log(`- ${label.name}`)
})
} else {
console.log('No labels found.')
}
},
)
}
function listMessages(auth) {
const gmail = google.gmail({ version: 'v1', auth })
const arr = []
gmail.users.messages.list(
{
userId: 'me',
},
(err, res) => {
if (err) return console.log('The API returned an error: ' + err)
const labels = res.data
const lab = labels.messages
lab.map((x) => arr.push(x.id))
},
)
for (let i = 0; i < arr.length; i++) {
gmail.users.messages.get(
{
id: i,
userId: 'me',
},
(err, res) => {
if (err) return console.log('The API returned an error: ' + err)
const label = res.data
console.log(i)
console.log(label)
},
)
}
}
There is small logical error
It should not be id: i, but id: arr[i]:
for (let i = 0; i < arr.length; i++) {
gmail.users.messages.get(
{
id: arr[i],
userId: 'me',
},
...
I am using SQL Server with Node.js. When the connection fails in first attempt the Node.js does not reattempt to connect. I am using setTimeout() to keep trying periodically until it connects.
const poolPromise = new sql.ConnectionPool(config.db);
poolPromise
.connect()
.then(pool => {
console.log('Connected to MSSQL');
return pool;
})
.catch(err => {
if (err && err.message.match(/Failed to connect to /)) {
console.log(new Date(), String(err));
// Wait for a bit, then try to connect again
setTimeout(function() {
console.log('Retrying first connect...');
poolPromise.connect().catch(() => {});
}, 5000);
} else {
console.error(new Date(), String(err.message));
}
});
The above code attempt to connect, fails and try for second time but does not continue for third, fourth and so on.
I wrote this small snippet that works. I wrapped connection part into a function and then invoke it using a recursive function.
In this example you'll see an infinity.
function sql() {
this.connect = function() {
return new Promise((resolve, reject) => reject("error connecting"));
}
}
function connect() {
return new Promise((resolve, reject) => {
// const poolPromise = new sql.ConnectionPool("config.db");
const poolPromise = new sql();
poolPromise
.connect()
.then(pool => {
console.log("connected");
resolve(pool);
})
.catch(err => {
console.error(err);
reject(err);
});
});
}
function establishConnection() {
var a = connect();
a.then(a => console.log("success"))
.catch(err => {
console.error("Retrying");
// I suggest using some variable to avoid the infinite loop.
setTimeout(establishConnection, 2000);
});
};
establishConnection();
After checking out the answers here I agree that callbacks are the way to go. I wrote the follow script to attempt to connect to MySQL until connection is established, and then to occasionally check that the connection is still valid, and if not, attempt connection again. I placed console.log's in a few places so that as things run you can see and understand what's happening.
var mysql = require('mysql');
var env = require('dotenv').config()
// ENVIRONMENT LOADS
var env = process.env.NODE_ENV.trim();
var host = process.env.MYSQL_HOST.trim();
var user = process.env.MYSQL_USER.trim();
var password = process.env.MYSQL_ROOT_PASSWORD.trim();
var database = process.env.MYSQL_DB.trim();
var port = process.env.MYSQL_PORT.trim();
console.log('\n\n********\n\nMySQL Credentials\n\n********\n\n');
if (env != 'production') {
console.log("Host: ", host, ":", port);
console.log("User: ", user);
console.log("Database: ", database);
console.log("Password: ", password);
}else{
console.log('Using Production Credentials');
}
console.log('\n\n************************\n\n');
let mysqlDB = null; // db handler
let connected = null; // default null / boolean
let connectFreq = 1000; // When database is disconnected, how often to attempt reconnect? Miliseconds
let testFreq = 5000; // After database is connected, how often to test connection is still good? Miliseconds
function attemptMySQLConnection(callback) {
console.log('attemptMySQLConnection')
if (host && user && database) {
mysqlDB = mysql.createPool({
host: host,
port: port, // Modified for Dev env
user: user,
password: password,
database: database,
connectionLimit: 300,
waitForConnections: true, // Default value.
queueLimit: 300, // Unlimited
acquireTimeout: 60000,
timeout: 60000,
debug: false
});
testConnection((result) => {
callback(result)
})
} else {
console.error('Check env variables: MYSQL_HOST, MYSQL_USER & MYSQL_DB')
callback(false)
}
}
function testConnection(cb) {
console.log('testConnection')
mysqlDB.query('SELECT 1 + 1 AS solution', (error, results, fields) => {
try {
if (error) {
throw new Error('No DB Connection');
} else {
if (results[0].solution) {
cb(true)
} else {
cb(false)
}
}
} catch (e) {
// console.error(e.name + ': ' + e.message);
cb(false)
}
});
}
function callbackCheckLogic(res) {
if (res) {
console.log('Connect was good. Scheduling next test for ', testFreq, 'ms')
setTimeout(testConnectionCB, testFreq);
} else {
console.log('Connection was bad. Scheduling connection attempt for ', connectFreq, 'ms')
setTimeout(connectMySQL, connectFreq);
}
}
function testConnectionCB() {
testConnection((result) => {
callbackCheckLogic(result);
})
}
function connectMySQL() {
attemptMySQLConnection(result => {
callbackCheckLogic(result);
});
}
connectMySQL(); // Start the process by calling this once
module.exports = mysqlDB;
I've been struggling with promises and .then(), I have managed to create my two promises and they run, I think, however the ordering is still wrong, the first promise gets an updated spreadsheet and the next one does stuff to that spreadsheet and sends mails with that sheets data, no matter what I do I can't get the ordering to be correct. The message "Done fetching latest Drive sheet.." always prints after the mailing rules have run but it should be before. The getNewSheet promise should run, and then the sendMails promise should. Why isn't getNewSheet() running and then .sendMails?
Please help me understand why this isn't running and what I am doing wrong with the promises, additionally why isnt the message method of the promise return their values? I assume because the promise wasn't resolved in the correct order.
Please note I am still learning and using environment variables for some variables, I have edited out what I can, its a bit long but I am curious if I need to do something further with the containing code/functions in each promise before it would successful.
EDITED: Edited code to be more accurate.
let getNewSheet = function () {
return new Promise(function (resolve, reject) {
const fs = require("fs");
const readline = require("readline");
const {
google
} = require("googleapis");
const SCOPES = "https://www.googleapis.com/auth/drive";
const TOKEN_PATH = "token.json";
fs.readFile("credentials.json", (err, content) => {
if (err) return console.log("Error loading client secret file:", err);
authorize(JSON.parse(content), getLatestListFromDrive);
});
function authorize(credentials, callback) {
const {
client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]
);
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return getAccessToken(oAuth2Client, callback);
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
});
}
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: "offline",
scope: SCOPES
});
console.log("Authorize this app by visiting this url:", authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question("Enter the code from that page here: ", code => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error("Error retrieving access token", err);
oAuth2Client.setCredentials(token);
fs.writeFile(TOKEN_PATH, JSON.stringify(token), err => {
if (err) console.error(err);
console.log("Token stored to", TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
function getLatestListFromDrive(auth) {
const drive = google.drive({
version: "v3",
auth
});
var fileId = process.env.NODE_DRIVE_FILE_ID;
var dest = fs.createWriteStream("" + appDir + "\\tmp\\feedbacklist.xlsx");
drive.files.export({
fileId: fileId,
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
}, {
responseType: "stream"
},
function (err, res) {
res.data
.on("end", () => {
})
.on("error", err => {
console.log("Error", err);
})
.pipe(dest);
}
);
}
resolve();
});
};
var path = require("path");
var appDir = path.dirname(require.main.filename);
let sendMails = function () {
return new Promise(function (resolve, reject) {
const XLSX = require("xlsx");
const workbook = XLSX.readFile("" + appDir + "\\tmp\\feedbacklist.xlsx", {
type: "binary",
cellDates: true,
dateNF: "yyyy-mm-dd;#"
});
const SheetNameList = workbook.SheetNames;
var rows = XLSX.utils.sheet_to_json(workbook.Sheets[SheetNameList[0]], {
raw: false
});
var raw = workbook.Sheets[SheetNameList[0]];
var today = new Date();
today = today.toISOString().slice(0, 10);
var dateYesterday = new Date();
dateYesterday.setDate(dateYesterday.getDate() - 1);
dateYesterday = dateYesterday.toISOString().slice(0, 10);
var dayYesterday = new Date();
var days = [
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
];
dayYesterday.setDate(dayYesterday.getDate() - 1);
dayYesterday = days[dayYesterday.getDay()];
var dateTomorrow = new Date();
dateTomorrow.setDate(dateTomorrow.getDate() + 1);
dateTomorrow = dateTomorrow.toISOString().slice(0, 10);
var dayTomorrow = new Date();
var futureDays = [
"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
];
dayTomorrow.setDate(dayTomorrow.getDate() + 1);
dayTomorrow = futureDays[dayTomorrow.getDay()];
var filteredRows = rows.filter(eachRow);
function eachRow(eachRow) {
return eachRow["tourDate"] === dateYesterday;
}
if (filteredRows != 0) {
for (x = 0; x < filteredRows.length; x++) {
console.log(filteredRows[x].emailAddress);
console.log(filteredRows[x].fName);
console.log(filteredRows[x].tourDate);
console.log(filteredRows[x].feedbacksent);
var nodemailer = require("nodemailer");
var transporter = nodemailer.createTransport({
host: process.env.NODEMAILER_SERVICE,
secure: false,
auth: {
user: process.env.NODEMAILER_USER,
pass: process.env.NODEMAILER_PASS
}
});
var mailOptions = {
from: process.env.NODEMAILER_FROM,
to: "" + filteredRows[x].emailAddress + "",
subject: "Hi",
html: "Hi ",
text: "Hi "
};
console.log("I've sent a feedback request to " + filteredRows[x].emailAddress);
transporter.sendMail(mailOptions, function (error, info) {
//check for console errors
if (error) {
console.log(error);
} else {
console.log(
"Feedback request sent to " +
filteredRows[x].emailAddress
);
};
})
}
} else {
console.log("No rows/records to process for yesterday..");
}
resolve();
});
};
getNewSheet(console.log("drive fetched")).then(function (result) {
console.log("mails sent");
return sendMails(result);
}).then(function () {
console.log("promises finished");
});
Call resolve and reject functions when the operations you had scheduled, were actually completed.
Remember that when dealing with asynchronous operations, the code you write is executed out of order, meaning not in the order it is written in the file.
Consider these examples:
Will not work
return new Promise(function (resolve, reject) {
let result
fs.someAsyncOperation(function (data) {
result = data
})
resolve(result) // the promise will resolve immediately with undefined value
}
Will work
return new Promise(function (resolve, reject) {
fs.someAsyncOperation(function (result) {
resolve(result) // the promise will resolve after async operation is completed
})
}
Your code should be reworked to wait for the results of asynchronous operation before calling resolve function.
Under your sendMessage promise function, the resolve should be inside the callback of the sendMail function since it is asynchronous.
transporter.sendMail(mailOptions, function(error,info){
resolve();
});
UPDATE
Async stuff sometimes gets real tricky especially if you're used to synchronous processes. When I started on software this got me confused to.
You're promise functions are good. The problem there was that you were resolving the send function before it actually did it's job.
So below is a simpler version of your code. Doing the same thing you did
// function that returns a message
// duration: 1s
function getMessage() {
return new Promise(resolve => {
setTimeout(() => resolve('important message'), 1000)
});
}
// function that send a message using sendEmail function
// duration should be based on sendEmail
// notice that the resolve is called inside the callback
function sendMessage(message) {
return new Promise((resolve, reject) => {
sendEmail(message, (err, result) => {
// do some stuff if email was sent
})
resolve();
});
}
// function try to send an email asynchronously using a callback
// duration: 0.5s
function sendEmail(message, callback) {
setTimeout(() => {
console.log('email sent!')
callback(null, 'message sent');
}, 500)
}
getMessage().then(result => {
return sendMessage(result);
}).then(() => {
console.log('should be done after email')
})
With the above code your terminal should show
should be done after email
email sent!
But with some modification on the sendMessage function
function sendMessage(message) {
return new Promise((resolve, reject) => {
sendEmail(message, (err, result) => {
resolve(result);
})
});
}
The terminal should now show
email sent!
should be done after email
Whenever I try to run the JS code with node in CMD I get this error:
Oh and the bot probably cannot be run since stackoverflow needs permission from mySQL database.
TypeError: Cannot read property 'length' of undefined
at Query._callback (C:\Users\George\Desktop\bot2\websitebot.js:138:9)
at Query.Sequence.end (C:\Users\George\node_modules\mysql\lib\protocol\sequences\Sequence.js:86:24)
at Query.ErrorPacket (C:\Users\George\node_modules\mysql\lib\protocol\sequences\Query.js:94:8)
at Protocol._parsePacket (C:\Users\George\node_modules\mysql\lib\protocol\Protocol.js:280:23)
at Parser.write (C:\Users\George\node_modules\mysql\lib\protocol\Parser.js:74:12)
at Protocol.write (C:\Users\George\node_modules\mysql\lib\protocol\Protocol.js:39:16)
at Socket.<anonymous> (C:\Users\George\node_modules\mysql\lib\Connection.js:109:28)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at readableAddChunk (_stream_readable.js:176:18)
This is the code I'm trying to run, I honestly have no idea why I am getting these errors. Incase someone here might help me:
//The required liberys for the bot to work
var Steam = require('steam')
var SteamUser = require('steam-user');
var SteamTotp = require('steam-totp');
var SteamConfirm = require('steamcommunity-mobile-confirmations');
var SteamTradeOffers = require('steam-tradeoffers');
var TradeOfferManager = require('steam-tradeoffer-manager');
var TOTP = require('onceler').TOTP;
var request = require('request');
var mysql = require('mysql');
var offers = new SteamTradeOffers();
var apik = 'xxx'; //The API Key of the bot.
var botsteamid = 'xxx'; //The SteamID of the bot.
var identitysecret = 'xxx'; //The identity secret of the bot.
var sharedsecret = 'xxx'; //The shared secret of the bot
var botusername = 'xxx';
var botpassword = 'xxx';
var admin = 'xxx'; //The steamid of the Admin.
var botid = 'xxx'; //The ID of the bot..
var pooling_interval = 10000; // 10 seconds by default, the bot checks for outgoing confirmations every X seconds, defined here
//Setting up device identity
var deviceid=SteamTotp.getDeviceID(botsteamid);
//Making the bot log in.
var details = {
"accountName" : botusername, // Bot username
"password" : botpassword, // Bot password
"twoFactorCode" : SteamTotp.generateAuthCode(sharedsecret)
};
var client = new SteamUser();
var manager = new TradeOfferManager({
"steam" : client,
"domain" : "localhost", //localhost
"language" : "en",
})
//Setting up the MySQL Connection - This is where I have errors.
var connection = mysql.createConnection({
host : 'xxx', // MYSQL , LEAVE IT AS LOCALHOST IF YOU RUN IT ON THE SAME SERVER AS THE WEBSITE AND DATABASE
user : 'xxx', // MYSQL USERNAME
password : 'xxx', // MYSQL PASSWORD
database : 'xxx', // MYSQL DATABASENAME
charset : 'utf8_general_ci'
});
connection.connect();
client.logOn(details);
//Checking mobile confirmations
function checkConfirmations(steamcommunityMobileConfirmations){
steamcommunityMobileConfirmations.FetchConfirmations((function (err, confirmations)
{
if (err)
{
console.log('Confirmations error: '+err);
if(err=='Error: 503') // This is an error you can most likely ignore, except if it's spammed a lot - To fix it simply restart the bot
{
}
if(err=='Error: Invalid protocol: steammobile:') // - To fix it simply restart the bot
{
// A fix should be coming soon!
}
return;
}
if(confirmations.length>0)
{
console.log('[SERVER] Received ' + confirmations.length + ' confirmations');
}
if ( ! confirmations.length)
{
return;
}
steamcommunityMobileConfirmations.AcceptConfirmation(confirmations[0], (function (err, result)
{
if (err)
{
console.log(err);
return;
}
console.log('[SERVER] Confirmation handling result: ' + result);
}).bind(this));
}).bind(this));
}
//Done with the functions, time to do commands.
//Logging the bot in
client.on('loggedOn', function(details)
{
client.on('webSession', function(sessionID, cookies){
manager.setCookies(cookies, function(err) {
if(err) {
console.log('setCookies error: '+err);
process.exit(1); // Fatal error since we couldn't get our API key
return;
}
var steamapi=manager.apiKey;
var SteamcommunityMobileConfirmations = require('steamcommunity-mobile-confirmations');
var steamcommunityMobileConfirmations = new SteamcommunityMobileConfirmations(
{
steamid: botsteamid,
identity_secret: identitysecret,
device_id: deviceid,
webCookie: cookies,
});
setInterval(function(){
checkConfirmations(steamcommunityMobileConfirmations)
}, pooling_interval);
console.log("[SERVER] The Bot has logged in!");
client.addFriend(admin);
client.setPersona(Steam.EPersonaState.LookingToTrade);
});
offers.setup({
sessionID: sessionID,
webCookie: cookies,
APIKey: apik
});
});
});
function checkWithdraw(){
connection.query("SELECT * FROM `withdraw` WHERE active=1 AND `botid`='"+botid+"' AND tradestatus='Queued' LIMIT 1", function(err, row, fields) {
if (!row.length) {
return;
}
var tradeid = row[0].id;
var sendItems = (row[0].assetids).split(',');
var item=[],num = 0;
for (i = 0; i < sendItems.length; i++) {
item[num] = {
appid: 730,
contextid: 2,
amount: 1,
assetid: sendItems[i]
}
num++;
}
offers.makeOffer ({
partnerSteamId: row[0].steamid,
accessToken: row[0].token,
itemsFromMe: item,
itemsFromThem: [],
message: 'Withdraw from '
},
function(err, response) {
if (err) {
console.log(err);
return;
}
console.log('Tradeoffer sent to ' + row[0].steamid);
tradeofferquery = response;
tradeofferid = (tradeofferquery['tradeofferid']);
connection.query('UPDATE `withdraw` SET `tradeid`=\''+tradeofferid+'\', `tradestatus`="Sent" WHERE `id`=\''+tradeid+'\'', function(err, row, fields) {});
})
});
}
function checkDeposit(){
connection.query("SELECT * FROM `deposits` WHERE `credited`=\'0\' AND `tradestatus`=\'Queued\' AND `botid`='"+botid+"' LIMIT 1", function(err, row, fields) {
if (!row.length) {
return
}
offers.getHoldDuration({partnerSteamId: row[0].steamid, accessToken: row[0].token}, function(err, response)
{
if (err)
{
return;
}
escrowduration = response;
thesd = (escrowduration['their']);
if(thesd === 0){
var tradeid = row[0].id;
var sendItems = (row[0].assetids).split(',');
var item=[],num = 0;
for (i = 0; i < sendItems.length; i++) {
item[num] = {
appid: 730,
contextid: 2,
amount: 1,
assetid: sendItems[i]
}
num++;
}
console.log(item);
offers.makeOffer ({
partnerSteamId: row[0].steamid,
accessToken: row[0].token,
itemsFromMe: [],
itemsFromThem: item,
message: 'Deposit to , code: ' + row[0].code
}, function(err, response) {
if (err) {
console.log(err);
return;
}
console.log('Tradeoffer sent to ' + row[0].steamid);
tradeofferquery = response;
tradeofferid = (tradeofferquery['tradeofferid']);
connection.query('UPDATE `deposits` SET `tradeid`=\''+tradeofferid+'\', `tradestatus`="Sent" WHERE `id`=\''+tradeid+'\'', function(err, row, fields) {});
})
} else {
connection.query('DELETE FROM `deposits` WHERE `steamid`=\''+ row[0].steamid +'\'', function(err, row, fields) {});
console.log('They are in escrow');
}
});
});
}
//Keeping track of sent offers.
manager.on('sentOfferChanged', function(offer, oldState) {
console.log("Offer #" + offer.id + " changed: " + TradeOfferManager.getStateName(oldState) + " -> " + TradeOfferManager.getStateName(offer.state));
connection.query('UPDATE `deposits` SET `tradestatus`=\''+TradeOfferManager.getStateName(offer.state)+'\' WHERE `tradeid`=\''+offer.id+'\'');
connection.query('UPDATE `withdraw` SET `tradestatus`=\''+TradeOfferManager.getStateName(offer.state)+'\' WHERE `tradeid`=\''+offer.id+'\'');
if(offer.state == TradeOfferManager.ETradeOfferState.Accepted) {
offer.getReceivedItems(function(err, items) {
if(err) {
console.log("Couldn't get received items: " + err);
} else {
items.forEach(function(item)
{
console.log('Recieved: ' + item.name);
connection.query('INSERT INTO `bank` (`botid`,`assetid`,`img`,`name`,`status`) VALUES (\''+botid+'\',\''+item.assetid+'\',\''+item.icon_url+'\',\''+item.market_name+'\',\'1\')', function(err, row, fields) {});
})
}
});
}
if(offer.state != (TradeOfferManager.ETradeOfferState.Accepted || TradeOfferManager.ETradeOfferState.Active)) {
connection.query('DELETE FROM `deposits` WHERE `tradeid`=\''+offer.id+'\'');
connection.query('DELETE FROM `withdraw` WHERE `tradeid`=\''+offer.id+'\'');
}
});
//Processing incomming offers
manager.on('newOffer', function(offer)
{
offer.decline(function(err)
{
console.log('[DEBUG] Declined Counter offer.');
if (err)
{
console.log('Decline error: '+err);
}
connection.query('DELETE FROM `deposits` WHERE `tradeid`=\''+offer.id+'\'');
connection.query('DELETE FROM `withdraw` WHERE `tradeid`=\''+offer.id+'\'');
});
});
setInterval(function() {
checkDeposit();
checkWithdraw();
}, 5000);
//Numeric and float
function is_float(mixed_var)
{
return +mixed_var === mixed_var && (!isFinite(mixed_var) || !! (mixed_var % 1));
}
function isNumeric(n){
return (typeof n == "number" && !isNaN(n));
}
//Setting up chat commands for the admin
client.on('friendMessage#'+admin+'', function(steamID, message)
{
console.log("[SERVER] Admin to Bot: " + message);
if((message.indexOf("ping") == 0) || (message.indexOf("/cmd") == 0))
{
checkDeposit();
client.chatMessage(admin, 'Pong!');
}
if((message.indexOf("pong") == 0) || (message.indexOf("/cmd") == 0))
{
checkWithdraw();
client.chatMessage(admin, 'Pong!');
}
if(message.indexOf("/code") == 0)
{
var code = SteamTotp.generateAuthCode(sharedsecret);
client.chatMessage(admin, '[SERVER] Your login code: '+code+'');
}
});
If i understod your question right this is where the error occur.
connection.query("SELECT * FROM `withdraw` WHERE active=1 AND `botid`='"+botid+"' AND tradestatus='Queued' LIMIT 1", function(err, row, fields) {
if (!row.length) {
return;
}
Im kind of new too the mysql area but i whould try to write the code something like this instead:
connection.query("SELECT * FROM `withdraw` WHERE active=1 AND `botid`= ? AND tradestatus='Queued' LIMIT 1",botid , function(err, rows, fields) {
if (!rows.length) {
return;
}
What ive done is changed the place where you input the "botid" to a "?" instead, that takes the value from the botid right before your function, and row is now rows just for code clearance.
As you said, i cant access your DB but i hope that this might work. Let me know!