UnhandledPromiseRejectionWarning: ReferenceError: message is not defined Discord.js - node.js

I'm in the process of creating a Discord bot that will read from a specific Google Sheets spreadsheet and this error keeps coming up as I am trying to integrate the Google Sheets functions. See Github Repo and please know I'm extremely new at Node.js.
For this function, I have created index.js to get everything running for both discord.js and the Google API:
require("dotenv").config();
const fs = require("fs");
const Discord = require("discord.js");
const client = new Discord.Client();
const readline = require('readline');
const { google } = require('googleapis');
const OAuth2Client = google.auth.OAuth2;
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
const TOKEN_PATH = 'token.json';
/**
* 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.
*/
const authorize = function (credentials, callback) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new OAuth2Client(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.
*/
const getNewToken = function (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 callback(err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
callback(oAuth2Client);
});
});
}
//pulls all commands and events
fs.readdir("./events/", (err, files) => {
files.forEach(file => {
const eventHandler = require(`./events/${file}`);
const eventName = file.split(".")[0];
client.on(eventName, (...args) => eventHandler(client, ...args));
});
});
client.login(process.env.BOT_TOKEN);
module.exports = {
authorize,
google
}
Sheets.js contains the readAll function for Sheets I want to use so when someone types in assignments!all the function should run:
const {authorize, google} = require('./index');
const fs = require("fs");
const readline = require('readline');
const Discord = require("discord.js");
const client = new Discord.Client();
const spreadsheetId = "1asvhCVI1sC6Q2cCuqdUrRqFHkH2VCr5FM7kWSm8VBE8";
//read entire spreadsheet
const readAll = (range) => {
fs.readFile('client_secret.json', (err, content) => {
if (err) return console.log('Error loading client secret file:', err);
// Authorize a client with credentials, then call the Google Sheets API.
authorize(JSON.parse(content), (auth) => {
const sheets = google.sheets({ version: 'v4', auth });
sheets.spreadsheets.values.get({
spreadsheetId, range
}, (err, res) => {
if (err) return console.log('The API returned an error:' + err);
const rows = res.data.values;
if (rows.length) {
// Print columns B through F.
rows.map((row) => {
message.reply(`Details: ${row[0]}, Link: ${row[1]}, Additional Link: ${row[2]}, Assigned To: ${row[3]}, Assigned On: ${row[4]}, Ready for QC on: ${row[5]}, QC Link: ${row[6]}`);
});
} else {
console.log('No data found.');
}
});
});
});
}
module.exports = {
readAll
};
As I plan to have the bot do more functions in the future, I have created a message.js file in an events folder to call commands from a commands folder:
const assignments = require('../commands/assignments');
const assign = require('../commands/assign');
const qc = require('../commands/qc');
const qcList = require('../commands/qcList');
const sheets = require('../sheets');
module.exports = (client, message) => {
if (message.content.startsWith('assignments!')) {
return assignments(message);
} else if (message.content.startsWith('assign!')) {
return assign(message);
} else if (message.content.startsWith('qc!')) {
return qc(message);
} else if (message.content.startsWith('qclist!')) {
return qcList(message);
}
};
Assignments.js actually tells the bot that when the word "all" is used after the ! in "assignments!", that is when it is suppose to run the ReadAll function on the specific tab in the Sheets spreadsheet:
// gets all assignments or assignments based on name given after !
module.exports = (message, google, authorize, readAll) => {
const sheets = require('../sheets');
//ex message: assignments!Brody
//gets name after ! and stores as member variable
var messageContent = message.content;
var member = messageContent.split('!')[1];
var member = member.toLowerCase();
//test reading spreadsheet
if(member === 'all') {
sheets.readAll("Active News Assignments!B2:F");
}
//error messages
if (!member) {
message.reply("You must add a name or 'all' after the command.");
}
};
Again, I am very new to Node.js and following these tutorials to create this bot. Please feel free point out anything obvious I'm missing or ways I can simplify my code once it's working as I know I will be adding more functions in the future.
https://levelup.gitconnected.com/making-a-custom-discord-bot-in-discord-js-1e17f2090919
https://www.ishaanrawat.com/integrate-google-sheets-api-with-node-js/

Related

Create folder returns undefined when trying to find file id

I am using Google drive API to create a folder and I want to return the id of this folder after creating it so I can make a folder tree. I don't understand why I am unable to do this, even using promises I consistently get the result of undefined for the id of the folder. This code returns undefined for the id of the folder. I don't want to use the list method to search the drive after creating the folder because it's slow. Any ideas?
const fs = require('fs');
const readline = require('readline');
const {google, run_v1} = require('googleapis');
const SCOPES = ['https://www.googleapis.com/auth/drive'];
const TOKEN_PATH = 'token.json';
const CLIENT_NAME = 'Bowie, David';
const ROOT_FOLDER_LOCATION = '11ljl1lk1lk';
function getCreds(){
return new Promise((resolve, reject) => {
fs.readFile('credentials.json', (err, content) => {
if (err) {
reject('error loading client secret');
} else{
resolve(JSON.parse(content));
}
})
})
}
function authorize(creds){
const {client_secret, client_id, redirect_uris} = creds.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
return new Promise((resolve, reject) => {
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) {
reject('this failed authorization');
} else {
oAuth2Client.setCredentials(JSON.parse(token));
resolve(oAuth2Client);
}
})
})
}
function makeRoot(auth) {
const drive = google.drive({version: 'v3', auth});
var fileMetadata = {
'name': CLIENT_NAME,
'mimeType': 'application/vnd.google-apps.folder',
'parents': [ROOT_FOLDER_LOCATION]
};
return new Promise((resolve, reject) => {
drive.files.create({
resource: fileMetadata,
fields: 'id'
}, function(err, file) {
if (err) {
reject(err);
} else {
resolve('Folder ID:', file.id);
}
})
})
}
async function doWork() {
try {
const response = await getCreds();
const oAuth2Client = await authorize(response);
const rootFolder = await makeRoot(oAuth2Client);
console.log(rootFolder);
} catch (err) {
console.log(err)
}
}
doWork();
The only output I get is "undefined"...
Modification points:
When you want to retrieve the folder ID from the created new folder using googleapis for Node.js, in the case of your script, you can retrieve it using file.data.id. Ref1, Ref2
But, in your script, when resolve('Folder ID:', file.id); is run, rootFolder of const rootFolder = await makeRoot(oAuth2Client); is Folder ID:.
When you want to retrieve the value of Folder ID:###folderId###, please modify as follows.
From:
resolve('Folder ID:', file.id);
To:
resolve("Folder ID:" + file.data.id);
If you want to separate the values of Folder ID: and ###folderId###, you can also use the following modification. In this case, you can retrieve the folder ID like const [text, rootFolder] = await makeRoot(oAuth2Client);.
resolve(["Folder ID:", file.data.id]);
Reference:
google-api-nodejs-client

Verify email content using mocha

I am using Node JS, mocha and googleapi writing tests to verify email content
When i run the googleapi as a standalone node js file i am able to get mails but when i integrate it with mocha tests i am not seeing any result, please help
test spec file (verify.js)
var checkEmail = require('../shared/checkEmail');
const { google } = require('googleapis');
var expect = require('chai').expect;
var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
const { isNullOrUndefined } = require('util');
chai.use(chaiAsPromised);
chai.should();
var emailRecords = new Array();
var content;
var auth;
describe('GMAIL Testing', function () {
before('Connect to Gmail server', function () {
content = checkEmail.getAuthentication();
auth = checkEmail.authorize(content);
// Random test data
var a = [{ "Id": "123", "MsgId": "34677", "Type": "aaa", "Subject": "subxxxx", "ToAddress": "abc#gmail.com", "ToName": "ABC", "DateCreated": "2020-07-09T18:25:38.047Z" }];
emailRecords.push(a);
var b = [{ "Id": "456", "MsgId": "34655", "Type": "bbb", "Subject": "subject", "ToAddress": "abc#gmail.com", "ToName": "ABC", "DateCreated": "2020-06-09T18:25:38.047Z" }];
emailRecords.push(b);
});
it('Gmail Verification', function () {
emailRecords.forEach(element => {
const gmail = google.gmail({ version: 'v1', auth });
var query = "from:noreply#somedomain.com " + element[0].MsgId;
console.log('getting mail '+ element[0].MsgId);
gmail.users.messages.list({
userId: 'me',
q: query
}, (err, res) => {
if (err) return console.log('The API returned an error: ' + err);
var mails = res.data.messages;
console.log('mail(s) found');
expect(mails.length).to.be.at.least(1);
});
console.log('completed search');
});
});
});
Utility File checkEmail.js Ref -> Gmail API
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
var base64 = require('js-base64').Base64;
const cheerio = require('cheerio');
var open = require('open');
const { isNullOrUndefined } = require('util');
var Mailparser = require('mailparser').MailParser;
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
const TOKEN_PATH = 'token.json';
module.exports = new class checkEmail {
getAuthentication() {
// Load client secrets from a local file.
console.log('getting auth');
this.content = JSON.parse(fs.readFileSync('shared/config/credentials.json', 'utf8'));
return this.content;
}
authorize(credentials) {
const { client_secret, client_id, redirect_uris } = credentials.installed;
const oAuth2Client = new google.auth.OAuth2(
client_id, client_secret, redirect_uris[0]);
var token = fs.readFileSync(TOKEN_PATH, 'utf-8');
if (token == isNullOrUndefined) {
token = getNewToken(oAuth2Client);
}
oAuth2Client.setCredentials(JSON.parse(token));
return oAuth2Client;
}
getNewToken(oAuth2Client) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
var newToken;
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);
});
newToken = token;
});
});
return newToken;
}
}
I have tried adding debug messages but nothing is printed nor any error is thrown, Please let me know if i am missing something
output after running tests
> .\node_modules.bin\mocha .\test\verify.js
Tests are appearing to be passed but console.log('mail(s) found');
didnt show up in the output
Your test is finishing before your network request finishes.
See this section in the mocha docs.
https://mochajs.org/#asynchronous-code
You need to either use the done callback or return a promise.
If you can use async/await I find this to be the easiest because an async function always returns a promise: https://mochajs.org/#using-async-await

how to refresh token on google oauth2 using firebase functions?

I developed an integration using Google Oauth2 inside firebase functions to access Google Sheets API. The integration works correctly but I'm having problems to make sure the refresh token is running correctly. The function stops working after the first token expires.
when this happens the following error occur:
Function execution started
Error: No refresh token is set.
at OAuth2Client.refreshTokenNoCache (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:161:19)
at OAuth2Client.refreshToken (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:142:25)
at OAuth2Client.getRequestMetadataAsync (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:256:28)
at OAuth2Client.requestAsync (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:329:34)
at OAuth2Client.request (/workspace/node_modules/googleapis-common/node_modules/google-auth-library/build/src/auth/oauth2client.js:323:25)
at createAPIRequestAsync (/workspace/node_modules/googleapis-common/build/src/apirequest.js:292:27)
at Object.createAPIRequest (/workspace/node_modules/googleapis-common/build/src/apirequest.js:43:9)
at Resource$Spreadsheets$Values.update (/workspace/node_modules/googleapis/build/src/apis/sheets/v4.js:601:37)
at exports.loadStripeData.functions.runWith.https.onRequest (/workspace/index.js:176:32)
at process._tickCallback (internal/process/next_tick.js:68:7)
I want to make sure the token refresh correctly and get stored on Firestore.
What am I doing wrong?
index.js:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {google} = require('googleapis');
const sheets = google.sheets('v4');
admin.initializeApp();
const CLIENT_ID = 'CLIENT_ID';
const CLIENT_SECRET = 'CLIENT_SECRETT';
const REDIRECT_URL = 'https://us-central1-MY-PROJECT.cloudfunctions.net/oauth2callback';
const SCOPES = ['https://www.googleapis.com/auth/spreadsheets'];
oauth2Client.on('tokens', (tokens) => {
if (tokens.refresh_token) {
try {
admin.firestore()
.collection('oauth2')
.doc('google')
.set({
tokens: tokens.refresh_token,
});
} catch (error) {
console.error(JSON.stringify(error));
}
}
});
/*asks user permission to access his spreadsheets*/
exports.authenticate = functions.https.onRequest((req, res) => {
const authorizeUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES.join(','),
});
res.send(`<html>click here: ${authorizeUrl}</html>`)
});
/*callback function for when the user finishes authenticating*/
exports.oauth2callback = functions.https.onRequest(async(req, res) => {
const code = req.query.code.toString() || '';
try {
await admin.firestore()
.collection('oauth2')
.doc('google')
.set({
code: decodeURIComponent(code)
});
} catch(error) {
res.send(JSON.stringify(error))
}
res.send('auth successfully. You can close this tab');
});
/* get token from Firestone to execute function*/
async function oauth2Auth() {
const doc = await admin.firestore()
.collection('oauth2')
.doc('google')
.get();
const credentials = doc.data();
if (credentials.code !== undefined) {
const response = await oauth2Client.getToken(credentials.code);
credentials.tokens = response.tokens;
delete credentials.code;
try {
await admin.firestore()
.collection('oauth2')
.doc('google')
.set({
tokens: credentials.tokens,
})
} catch (error) {
console.error(error);
}
}
oauth2Client.setCredentials(credentials.tokens);
}
/*function that requires google sheets api*/
exports.mainFunction = functions.https.onRequest(async(req, res) => {
oauth2Auth();
//do main function
});
Finally discovered the problem!
You only get the refreshing token in the first time you ask for authorization. So if you're don't save it correctly you have to ask permission again.
To solve it:
when redirecting the user to the authorization URL add the following parameters to have sure you get the refreshing token:
access_type=offline&prompt=consent
to save the refreshing token:
oauth2Client.on('tokens', async(tokens:any) => {
if (tokens.refresh_token) {
try {
const authorization = await oauth2Client.getToken(tokens.refresh_token);
await admin.firestore()
.collection('collectionName')
.doc(docId)
.update({
token: authorization.tokens
})
} catch (error) {
console.error(JSON.stringify(error));
}
}
});

Issue with Google oAuth2 callback using Firebase functions

I would like use Firebase Functions to use the Google Developer API. Authentification is required to use this API.
I follow the doc: https://github.com/googleapis/google-api-nodejs-client
I have some troubles to get the authorization code in the callback url.
var {google} = require('googleapis');
google.options({ auth: oauth2Client });
var oauth2Client = new google.auth.OAuth2(
'XXXX.apps.googleusercontent.com',
'XXXX',
'https://us-central1-XXXX.cloudfunctions.net/oauth2callback'
);
function generateAuthenticationUrl() {
return oauth2Client.generateAuthUrl({
access_type: 'offline',
prompt: 'consent',
scope: 'https://www.googleapis.com/auth/androidpublisher'
});
}
exports.oauth2Callback = functions.https.onRequest((req, res) => {
console.log(req.query.code);
const code = req.query.code;
//do something
return null;
});
exports.hello = functions.https.onRequest((req, res) => {
var url = generateAuthenticationUrl();
console.log(url);
//-> url print in the console is : https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fandroidpublisher&response_type=code&client_id=XXXXX-XXX.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fus-central1-XXX.cloudfunctions.net%2Foauth2callback
res.redirect(url);
});
Redirect url is set in the Google Console Developer:
When I call the url https://us-central1-XXX.cloudfunctions.net/hello, I got "Error: could not handle the request" and "finished with status: 'timeout'" in the Firebase logs.
What's wrong?
I found a solution.
Full code using JWT to authenticate, then get the list of app's reviews:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase);
var {google} = require('googleapis');
const serviceAccount = require('./client_secret.json');
const { JWT } = require('google-auth-library');
const getAuthorizedClient = () => new JWT({
email: serviceAccount.client_email,
key: serviceAccount.private_key,
scopes: ['https://www.googleapis.com/auth/androidpublisher']
});
const getAndroidpublisher = () => google.androidpublisher({
version: 'v3',
auth: getAuthorizedClient()
});
const requestProductValidation = () => new Promise((resolve, reject) => {
getAndroidpublisher().reviews.list({
packageName: "com.my.packagename"
}, (err, response) => {
if (err) {
console.log(`The API returned an error: ${err}`);
resolve({status: "Error"});
} else {
return resolve(response);
}
});
});
exports.hello = functions.https.onRequest((req, res) => {
return requestProductValidation();
});

Google Drive API Service Account inside domain

I have to download/upload/delete files from a folder on Drive with a Node.js server. That folder is inside the G Suite of the company, and only a few people in the company has access.
I have to use a Service Account to do this, the question is: is it possible? How can I do that?
I already read https://developers.google.com/drive/v2/web/delegation and https://developers.google.com/identity/protocols/OAuth2ServiceAccount
but I don't know if it is possible give permissions to a service account to access a folder inside the domain of the company, because the service account is #developer.gserviceaccount.com and the domain of the company is other, so gives me an error when I try to add that service account to the folder.
If you could guide me on this, I'll be very greatful.
Thanks!
You can use an oAuth token with the rights scope(s):
const path = require('path');
module.exports = (app) => {
const factory = {};
factory.connect = (done) => {
const fs = require('fs');
const google = require('googleapis');
const googleAuth = require('google-auth-library');
const SCOPES = [
'https://www.googleapis.com/auth/drive.metadata.readonly'
];
const TOKEN_DIR = path.resolve(app.root, 'server','config');
const TOKEN_PATH = path.resolve(TOKEN_DIR,'token.json');
const creds = require(path.resolve(app.root, 'server', 'config', 'google_oauth.json'));
authorize(creds, (ret) => {
done(null, ret);
});
/**
* 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 clientSecret = credentials.installed.client_secret;
const clientId = credentials.installed.client_id;
const redirectUrl = credentials.installed.redirect_uris[0];
const auth = new googleAuth();
const oauth2Client = new auth.OAuth2(clientId, clientSecret, redirectUrl);
// Check if we have previously stored a token.
fs.readFile(TOKEN_PATH, function (err, token) {
if (err) {
console.error('[ERROR] Unable to read token', 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) {
const authUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
console.log('Authorize this app by visiting this url: ', authUrl);
const readline = require('readline');
const 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);
}
};
return factory
};
and then factory.connect(done) will give done an auth to use googleapis:
const google = require('googleapis');
const service = google.drive('v3');
service.files.list({
auth,
pageSize: 10,
fields: 'nextPageToken, files(id, name)'
}, step);

Resources