Created
April 6, 2023 09:31
-
-
Save OctaneInteractive/8a96cb6bc5a48bb338548d78762e8a68 to your computer and use it in GitHub Desktop.
Methods to interact with various Google API endpoints
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { | |
} = require('googleapis') | |
const stream = require('stream') | |
const configurationsForApplication = require('../../common/configurations') | |
const RedisFactory = require('../../modules/redis') | |
const clientForRedis = RedisFactory(configurationsForApplication.databases.Redis) | |
/** | |
* Export an object containing methods for various Google APIs. | |
* @param {String} typeOfAction The type of action performed by the User, used in the selection of the appropriate redirect URL. | |
*/ | |
module.exports = ({ | |
typeOfAction | |
}) => { | |
const Google = {} | |
/** | |
* Authentication. | |
*/ | |
Google.createAuthorizationUrl = ({ | |
scopes, | |
state | |
}) => { | |
return new Promise(resolve => { | |
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction)) | |
const authorizationUrl = oAuth2Client.generateAuthUrl({ | |
access_type: 'offline', | |
response_type: 'code', | |
prompt: 'consent', | |
redirect_uri: getRedirectUrl(typeOfAction), | |
scope: JSON.parse(scopes).join(' '), | |
state: state, | |
include_granted_scopes: true | |
}) | |
return resolve({ | |
authorizationUrl: authorizationUrl | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Create an authorized OAuth2 client from an existing Access Token [and Refresh Token, if appropriate]. | |
* @param {String} userId ID of the User. | |
* @return Object OAuth2 object. | |
*/ | |
Google.createAuthClient = ({ | |
userId | |
}) => { | |
return new Promise(resolve => { | |
Google.getAccessTokenForGoogleAccount({ | |
userId | |
}) | |
.then(oAuth2Client => resolve(oAuth2Client)) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.connectViaGoogleAccount = ({ | |
codeOfAuthentication | |
}) => { | |
return new Promise((resolve, reject) => { | |
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction)) | |
oAuth2Client.getToken(codeOfAuthentication, async (error, tokens) => { | |
if (error) return reject(new Error(error)) | |
await oAuth2Client.setCredentials(tokens) | |
const user = await Google.getPersonFromGooglePeople(oAuth2Client) | |
return resolve({ | |
...user, | |
accessToken: tokens.access_token, | |
refreshToken: tokens.refresh_token | |
}) | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.connectViaGoogleDrive = ({ | |
codeOfAuthentication | |
}) => { | |
return new Promise(resolve => { | |
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction)) | |
oAuth2Client.getToken(codeOfAuthentication, (error, tokens) => { | |
if (error) return reject(new Error(error)) | |
oAuth2Client.setCredentials(tokens) | |
return resolve({ | |
accessToken: tokens.access_token, | |
refreshToken: tokens.refresh_token | |
}) | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.connectViaGoogleGmail = ({ | |
codeOfAuthentication | |
}) => { | |
return new Promise(resolve => { | |
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction)) | |
oAuth2Client.getToken(codeOfAuthentication, (error, tokens) => { | |
if (error) return reject(new Error(error)) | |
oAuth2Client.setCredentials(tokens) | |
return resolve({ | |
accessToken: tokens.access_token, | |
refreshToken: tokens.refresh_token | |
}) | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Get the Access Token. In addition, the Access Token and Refresh Token are kept up to date via the Google API, and then stored on Redis. | |
* @param {String} userId ID of the User. | |
* @return Object OAuth2 object. | |
*/ | |
Google.getAccessTokenForGoogleAccount = ({ | |
userId | |
}) => { | |
return new Promise((resolve, reject) => { | |
const oAuth2Client = new google.auth.OAuth2(process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_ID, process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_CLIENT_SECRET, getRedirectUrl(typeOfAction)) | |
clientForRedis.get(`accessTokenForGoogleAccount:${userId}`, (error, accessToken) => { | |
if (error) return reject(new Error(`Error with the Access Token: ${error}`)) | |
if (accessToken) { | |
oAuth2Client.setCredentials({ | |
access_token: accessToken.replace(/"/g, "") | |
}) | |
return resolve(oAuth2Client) | |
} else { | |
Google.getRefreshTokenForGoogleAccount({ | |
userId | |
}) | |
.then(async refreshToken => { | |
await oAuth2Client.setCredentials({ | |
refresh_token: refreshToken.replace(/"/g, "") | |
}) | |
await oAuth2Client.getAccessToken((error, token) => { | |
if (error) return reject(new Error(error)) | |
clientForRedis.setex(`accessTokenForGoogleAccount:${userId}`, 3600, JSON.stringify(token)) | |
}) | |
return resolve(oAuth2Client) | |
}) | |
} | |
}) | |
}) | |
} | |
Google.setAccessTokenForGoogleAccount = ({ | |
userId, | |
accessToken | |
}) => { | |
return new Promise(resolve => { | |
clientForRedis.setex(`accessTokenForGoogleAccount:${userId}`, 3600, JSON.stringify(accessToken)) | |
return resolve({ | |
status: true | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.getRefreshTokenForGoogleAccount = ({ | |
userId | |
}) => { | |
return new Promise(resolve => { | |
clientForRedis.get(`refreshTokenForGoogleAccount:${userId}`, (error, refreshToken) => { | |
if (error) return reject(new Error(`Error with the Refresh Token: ${error}`)) | |
if (refreshToken) { | |
return resolve(refreshToken) | |
} | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.setRefreshTokenForGoogleAccount = ({ | |
userId, | |
refreshToken | |
}) => { | |
return new Promise(resolve => { | |
clientForRedis.setex(`refreshTokenForGoogleAccount:${userId}`, 604800, JSON.stringify(refreshToken)) | |
return resolve({ | |
status: true | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.deleteAccessTokenForGoogleAccount = ({ | |
userId | |
}) => { | |
return new Promise((resolve, reject) => { | |
clientForRedis.del(`accessTokenForGoogleAccount:${userId}`, error => { | |
if (error) { | |
return reject(new Error(error)) | |
} else { | |
return resolve({ | |
status: true | |
}) | |
} | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.deleteRefreshTokenForGoogleAccount = ({ | |
userId | |
}) => { | |
return new Promise((resolve, reject) => { | |
clientForRedis.del(`refreshTokenForGoogleAccount:${userId}`, error => { | |
if (error) { | |
return reject(new Error(error)) | |
} else { | |
return resolve({ | |
status: true | |
}) | |
} | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* People. | |
*/ | |
/** | |
* | |
* @param {Object} OAuth2 object. | |
* @return Object An object containing identifying data of the person in question, via the People API. | |
*/ | |
Google.getPersonFromGooglePeople = (oAuth2Client) => { | |
return new Promise( async resolve => { | |
const service = google.people({ | |
version: 'v1', | |
auth: oAuth2Client | |
}) | |
const { | |
data | |
} = await service.people.get({ | |
resourceName: 'people/me', | |
personFields: 'names,emailAddresses' | |
}) | |
const name = data.names[0] | |
const emailAddress = data.emailAddresses[0].value | |
return resolve({ | |
firstName: name.givenName, | |
lastName: name.familyName, | |
nameOfPerson: name.displayName, | |
email: emailAddress | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Drive. | |
*/ | |
/** | |
* Lists the names and IDs of up to 10 files. | |
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client. | |
*/ | |
Google.getAllFilesFromDrive = ({ | |
userId, | |
pageToken | |
}) => { | |
return new Promise( async resolve => { | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const drive = google.drive({ | |
version: 'v3', | |
auth: oAuth2Client | |
}) | |
const response = await drive.files.list({ | |
corpora: 'user', | |
pageSize: 10, | |
orderBy: 'createdTime desc', | |
// q: "name='elvis233424234'", | |
pageToken: pageToken ? pageToken : '', | |
// Fields ( https://developers.google.com/drive/api/v3/reference/files ). | |
fields: 'nextPageToken,files(id,name,iconLink,mimeType,size,thumbnailLink,createdTime)' | |
}) | |
const filesFromDrive = response.data.files | |
if (filesFromDrive.length) { | |
const files = [] | |
filesFromDrive.forEach(file => { | |
files.push({ | |
id: file.id, | |
name: file.name, | |
iconLink: file.iconLink, | |
mimeType: file.mimeType, | |
size: file.size, | |
thumbnailLink: file.thumbnailLink, | |
createdAt: (() => { | |
const createdAt = new Date(file.createdTime) | |
return createdAt.toISOString() | |
})() | |
}) | |
}) | |
return await resolve(files) | |
} | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.uploadFileToDrive = ({ userId, fileToUpload }) => { | |
return new Promise( async resolve => { | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const bufferStream = new stream.PassThrough() | |
bufferStream.end(fileToUpload.buffer) | |
const drive = google.drive({ | |
version: 'v3', | |
auth: oAuth2Client | |
}) | |
drive.files.create({ | |
resource: { | |
name: fileToUpload.originalname | |
}, | |
media: { | |
mimeType: fileToUpload.mimetype, | |
body: bufferStream | |
}, | |
fields: 'id,name,iconLink,mimeType,size,createdTime' | |
}) | |
.then(response => { | |
const file = response.data | |
return resolve({ | |
id: file.id, | |
name: file.name | |
}) | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
Google.getFileFromDrive = ({ userId, fileId }) => { | |
return new Promise( async resolve => { | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const drive = google.drive({ | |
version: 'v3', | |
auth: oAuth2Client | |
}) | |
drive.files.get({ | |
fileId: fileId, | |
fields: '*' | |
}) | |
.then(response => { | |
console.info('Google.getFileFromDrive()', response.data) | |
const file = response.data.file | |
return resolve(file) | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Gmail. | |
*/ | |
Google.getAllLabelsFromGmail = ({ userId }) => { | |
return new Promise( async resolve => { | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const gmail = google.gmail({ | |
version: 'v1', | |
auth: oAuth2Client | |
}) | |
const response = await gmail.users.labels.list({ | |
userId: 'me' | |
}) | |
const labelsFromGmail = response.data.labels | |
if (labelsFromGmail.length) { | |
const labels = [] | |
labelsFromGmail.forEach(label => { | |
if (label.type === 'user') { | |
labels.push({ | |
id: label.id, | |
name: label.name | |
}) | |
} | |
}) | |
return resolve(labels) | |
} | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Messages that have the chosen Labels assigned to them. | |
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client. | |
* @param {Array} labels An array of ID values of the Labels chosen by the User. | |
* @return Object An object containing identifying data of the Messages that have the chosen Labels assigned to them, via the Gmail API. | |
*/ | |
Google.getAllMessagesByLabelsFromGmail = ({ userId, labels, pageToken }) => { | |
return new Promise( async resolve => { | |
const arrayOfLabels = JSON.parse(labels) | |
const messages = [] | |
if (arrayOfLabels.length > 0) { | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const gmail = google.gmail({ | |
version: 'v1', | |
auth: oAuth2Client | |
}) | |
// List ( https://developers.google.com/gmail/api/reference/rest/v1/users.messages/list ). | |
const messagesAsList = await gmail.users.messages.list({ | |
userId: 'me', | |
labelIds: arrayOfLabels, | |
maxResults: 10, | |
...(pageToken && { | |
pageToken: pageToken | |
}) | |
}) | |
if (messagesAsList.data.resultSizeEstimate > 0) { | |
for await (const messageAsList of messagesAsList.data.messages) { | |
const message = await Google.getMessagesByIdFromGmail({ userId, messageFromList: messageAsList }) | |
messages.push(message) | |
} | |
return resolve({ | |
resultSizeEstimate: messagesAsList.data.resultSizeEstimate, | |
nextPageToken: messagesAsList.data.nextPageToken, | |
messages | |
}) | |
} | |
} | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Message by ID. | |
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client. | |
* @param {Array} messageFromList An object containing the ID values for the messages and their threads. | |
* @return Object An object of the Message. | |
*/ | |
Google.getMessagesByIdFromGmail = async ({ userId, messageFromList }) => { | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const gmail = google.gmail({ | |
version: 'v1', | |
auth: oAuth2Client | |
}) | |
// Get ( https://developers.google.com/gmail/api/reference/rest/v1/users.messages/get ). | |
const messageFromGmail = await gmail.users.messages.get({ | |
userId: 'me', | |
id: messageFromList.id | |
}) | |
/** | |
* Here, `parts[0]` is plain text, and `parts[1]` is HTML. | |
* Also, the `parts` array isn't guaranteed to be available. | |
* @param {Object} message An object containing a wealth of data. | |
* @return String A string representative of the body of the message. | |
*/ | |
function getMessage (message) { | |
const messageAsText = message.data.payload.parts?.find(part => part.mimeType === 'text/plain') // Part 0. | |
const messageAsCode = message.data.payload.parts?.find(part => part.mimeType === 'text/html') // Part 1. | |
if (messageAsCode) { | |
return new Buffer.from(messageAsCode.body.data, 'base64').toString('utf-8') | |
} else if (messageAsText) { | |
return new Buffer.from(messageAsText.body.data, 'base64').toString('utf-8') | |
} else { | |
return message.data.snippet | |
} | |
} | |
const getField = fieldToFind => { | |
const [field] = messageFromGmail.data.payload.headers.filter(header => header.name === fieldToFind) | |
return field.value | |
} | |
return { | |
id: messageFromList.id, | |
threadId: messageFromList.threadId, | |
from: (() => { | |
return getField('From') | |
})(), | |
subject: (() => { | |
return getField('Subject') | |
})(), | |
body: await getMessage(messageFromGmail), | |
createdAt: (() => { | |
const createdAt = new Date(getField('Date')) | |
return createdAt.toISOString() | |
})() | |
} | |
} | |
/** | |
* Remove the Labels assigned to the Message. | |
* @param {String} userId Used by `createAuthClient()` to create an authorized OAuth2 client. | |
* @param {Array} labels An array of ID values of the Labels chosen by the User. | |
* @return {String} A string of the ID of the Message. | |
*/ | |
Google.removeLabelsAssignedToMessageFromGmail = ({ userId, messageId, labels }) => { | |
return new Promise( async resolve => { | |
const arrayOfLabels = JSON.parse(labels) | |
const oAuth2Client = await Google.createAuthClient({ | |
userId | |
}) | |
const gmail = google.gmail({ | |
version: 'v1', | |
auth: oAuth2Client | |
}) | |
gmail.users.messages.modify({ | |
userId: 'me', | |
id: messageId, | |
removeLabelIds: arrayOfLabels | |
}) | |
return resolve({ | |
id: messageId | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* YouTube. | |
*/ | |
Google.getYouTubeVideoByID = (videoId) => { | |
return new Promise(resolve => { | |
let youtube = google.youtube({ | |
version: 'v3', | |
auth: process.env.UNIQUE_NAMESPACE_GOOGLE_YOUTUBE_API_KEY | |
}) | |
// YouTube Data API ( https://developers.google.com/youtube/v3/docs/videos ). | |
return youtube.search.list({ | |
part: 'snippet', | |
q: `v=${videoId}` | |
}) | |
.then(response => { | |
if (response.data.items.length > 0) { | |
let video = response.data.items[0].snippet | |
return resolve({ | |
id: videoId, | |
title: video.title, | |
note: video.description, | |
createdAt: (() => { | |
const createdAt = new Date(video.publishedAt) | |
return createdAt.toISOString() | |
})() | |
}) | |
} | |
}) | |
}) | |
.catch(error => { | |
return new Error(error) | |
}) | |
} | |
/** | |
* Utilities. | |
*/ | |
/** | |
* Get the appropriate URL based on the action performed by the User. | |
* @param {String} typeOfAction The type of action performed by the User. | |
* @return {String} URL. | |
*/ | |
function getRedirectUrl (typeOfAction) { | |
switch (typeOfAction) { | |
case 'isNewUserViaGoogleAccount': | |
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[1] | |
case 'isExistingUserViaGoogleAccount': | |
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[2] | |
case 'authorizeGoogleDrive': | |
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[3] | |
case 'authorizeGoogleGmail': | |
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[4] | |
case 'isDefault': | |
return process.env.UNIQUE_NAMESPACE_GOOGLE_SIGN_IN_API_REDIRECT_URL.split(',')[0] | |
} | |
} | |
return Google | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It took me some time to figure out how to work with the different Google API, so I'm sharing this with anyone who's in the same situation I was a few months ago.