integrations/src/facebook/controller.ts
import { FacebookAdapter } from 'botbuilder-adapter-facebook-erxes';
import {
debugBase,
debugError,
debugFacebook,
debugRequest,
debugResponse
} from '../debuggers';
import Accounts from '../models/Accounts';
import Integrations from '../models/Integrations';
import { getConfig, getEnv, sendRequest } from '../utils';
import loginMiddleware from './loginMiddleware';
import { Comments, Customers, Posts } from './models';
import receiveComment from './receiveComment';
import receiveMessage from './receiveMessage';
import receivePost from './receivePost';
import { FACEBOOK_POST_TYPES } from './constants';
import {
getPageAccessToken,
getPageAccessTokenFromMap,
getPageList,
subscribePage
} from './utils';
const init = async app => {
app.get('/fblogin', loginMiddleware);
app.post('/facebook/create-integration', async (req, res, next) => {
debugRequest(debugFacebook, req);
const { accountId, integrationId, data, kind } = req.body;
const facebookPageIds = JSON.parse(data).pageIds;
const account = await Accounts.getAccount({ _id: accountId });
const integration = await Integrations.create({
kind,
accountId,
erxesApiId: integrationId,
facebookPageIds
});
const ENDPOINT_URL = getEnv({ name: 'ENDPOINT_URL' });
const DOMAIN = getEnv({ name: 'DOMAIN' });
debugFacebook(`ENDPOINT_URL ${ENDPOINT_URL}`);
if (ENDPOINT_URL) {
// send domain to core endpoints
try {
await sendRequest({
url: `${ENDPOINT_URL}/register-endpoint`,
method: 'POST',
body: {
domain: DOMAIN,
facebookPageIds,
fbPageIds: facebookPageIds
}
});
} catch (e) {
await Integrations.deleteOne({ _id: integration._id });
return next(e);
}
}
const facebookPageTokensMap: { [key: string]: string } = {};
for (const pageId of facebookPageIds) {
try {
const pageAccessToken = await getPageAccessToken(pageId, account.token);
facebookPageTokensMap[pageId] = pageAccessToken;
try {
await subscribePage(pageId, pageAccessToken);
debugFacebook(`Successfully subscribed page ${pageId}`);
} catch (e) {
debugError(
`Error ocurred while trying to subscribe page ${e.message || e}`
);
return next(e);
}
} catch (e) {
debugError(
`Error ocurred while trying to get page access token with ${e.message ||
e}`
);
return next(e);
}
}
integration.facebookPageTokensMap = facebookPageTokensMap;
await integration.save();
debugResponse(debugFacebook, req);
return res.json({ status: 'ok ' });
});
app.get('/facebook/get-pages', async (req, res, next) => {
debugRequest(debugFacebook, req);
const { kind, accountId } = req.query;
const account = await Accounts.getAccount({ _id: req.query.accountId });
const accessToken = account.token;
let pages = [];
try {
pages = await getPageList(accessToken, kind);
} catch (e) {
if (!e.message.includes('Application request limit reached')) {
await Integrations.updateOne(
{ accountId },
{ $set: { healthStatus: 'account-token', error: `${e.message}` } }
);
}
debugError(`Error occured while connecting to facebook ${e.message}`);
return next(e);
}
debugResponse(debugFacebook, req, JSON.stringify(pages));
return res.json(pages);
});
app.get('/facebook/get-post', async (req, res) => {
debugFacebook(
`Request to get post data with: ${JSON.stringify(req.query)}`
);
const { erxesApiId } = req.query;
const post = await Posts.getPost({ erxesApiId }, true);
return res.json({
...post
});
});
app.get('/facebook/get-status', async (req, res) => {
const { integrationId } = req.query;
const integration = await Integrations.findOne({
erxesApiId: integrationId
});
let result = {
status: 'healthy'
} as any;
if (integration) {
result = {
status: integration.healthStatus || 'healthy',
error: integration.error
};
}
return res.send(result);
});
app.get('/facebook/get-comments-count', async (req, res) => {
debugFacebook(
`Request to get post data with: ${JSON.stringify(req.query)}`
);
const { postId, isResolved = false } = req.query;
const post = await Posts.getPost({ erxesApiId: postId }, true);
const commentCount = await Comments.countDocuments({
postId: post.postId,
isResolved
});
const commentCountWithoutReplies = await Comments.countDocuments({
postId: post.postId,
isResolved,
parentId: null
});
return res.json({
commentCount,
commentCountWithoutReplies
});
});
app.get('/facebook/get-customer-posts', async (req, res) => {
debugFacebook(
`Request to get customer post data with: ${JSON.stringify(req.query)}`
);
const { customerId } = req.query;
const customer = await Customers.findOne({ erxesApiId: customerId });
if (!customer) {
return res.end();
}
const result = await Comments.aggregate([
{ $match: { senderId: customer.userId } },
{
$lookup: {
from: 'posts_facebooks',
localField: 'postId',
foreignField: 'postId',
as: 'post'
}
},
{
$unwind: {
path: '$post',
preserveNullAndEmptyArrays: true
}
},
{
$addFields: {
conversationId: '$post.erxesApiId'
}
},
{
$project: { _id: 0, conversationId: 1 }
}
]);
const conversationIds = result.map(conv => conv.conversationId);
return res.send(conversationIds);
});
app.get('/facebook/get-comments', async (req, res) => {
debugFacebook(`Request to get comments with: ${JSON.stringify(req.query)}`);
const { postId, commentId, senderId, isResolved } = req.query;
const post = await Posts.getPost({ erxesApiId: postId });
const query: {
postId: string;
isResolved?: boolean;
parentId?: string;
senderId?: string;
} = {
postId: post.postId
};
query.isResolved = isResolved === 'false' ? false : true;
let { limit } = req.query;
limit = parseInt(limit, 10);
if (senderId !== 'undefined') {
const customer = await Customers.findOne({ erxesApiId: senderId });
if (!customer) {
return res.end();
}
query.senderId = customer.userId;
} else {
query.parentId = commentId !== 'undefined' ? commentId : null;
}
const result = await Comments.aggregate([
{
$match: query
},
{
$lookup: {
from: 'customers_facebooks',
localField: 'senderId',
foreignField: 'userId',
as: 'customer'
}
},
{
$unwind: {
path: '$customer',
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'posts_facebooks',
localField: 'postId',
foreignField: 'postId',
as: 'post'
}
},
{
$unwind: {
path: '$post',
preserveNullAndEmptyArrays: true
}
},
{
$lookup: {
from: 'comments_facebooks',
localField: 'commentId',
foreignField: 'parentId',
as: 'replies'
}
},
{
$addFields: {
commentCount: { $size: '$replies' },
'customer.avatar': '$customer.profilePic',
'customer._id': '$customer.erxesApiId',
conversationId: '$post.erxesApiId'
}
},
{ $sort: { timestamp: -1 } },
{ $limit: limit }
]);
return res.json(result.reverse());
});
const accessTokensByPageId = {};
const getAdapter = async (): Promise<any> => {
const FACEBOOK_VERIFY_TOKEN = await getConfig('FACEBOOK_VERIFY_TOKEN');
const FACEBOOK_APP_SECRET = await getConfig('FACEBOOK_APP_SECRET');
if (!FACEBOOK_VERIFY_TOKEN || !FACEBOOK_APP_SECRET) {
return debugBase('Invalid facebook config');
}
return new FacebookAdapter({
verify_token: FACEBOOK_VERIFY_TOKEN,
app_secret: FACEBOOK_APP_SECRET,
getAccessTokenForPage: async (pageId: string) => {
return accessTokensByPageId[pageId];
}
});
};
// Facebook endpoint verifier
app.get('/facebook/receive', async (req, res) => {
const FACEBOOK_VERIFY_TOKEN = await getConfig('FACEBOOK_VERIFY_TOKEN');
// when the endpoint is registered as a webhook, it must echo back
// the 'hub.challenge' value it receives in the query arguments
if (req.query['hub.mode'] === 'subscribe') {
if (req.query['hub.verify_token'] === FACEBOOK_VERIFY_TOKEN) {
res.send(req.query['hub.challenge']);
} else {
res.send('OK');
}
}
});
app.post('/facebook/receive', async (req, res, next) => {
const data = req.body;
if (data.object !== 'page') {
return;
}
const adapter = await getAdapter();
for (const entry of data.entry) {
// receive chat
if (entry.messaging) {
debugFacebook(`Received messenger data ${JSON.stringify(data)}`);
adapter
.processActivity(req, res, async context => {
const { activity } = await context;
if (!activity || !activity.recipient) {
next();
}
const pageId = activity.recipient.id;
const integration = await Integrations.getIntegration({
$and: [
{ facebookPageIds: { $in: pageId } },
{ kind: 'facebook-messenger' }
]
});
await Accounts.getAccount({ _id: integration.accountId });
const { facebookPageTokensMap } = integration;
try {
accessTokensByPageId[pageId] = getPageAccessTokenFromMap(
pageId,
facebookPageTokensMap
);
} catch (e) {
debugFacebook(
`Error occurred while getting page access token: ${e.message}`
);
return next();
}
await receiveMessage(activity);
debugFacebook(
`Successfully saved activity ${JSON.stringify(activity)}`
);
})
.catch(e => {
debugFacebook(
`Error occurred while processing activity: ${e.message}`
);
res.end('success');
});
}
// receive post and comment
if (entry.changes) {
for (const event of entry.changes) {
if (event.value.item === 'comment') {
debugFacebook(
`Received comment data ${JSON.stringify(event.value)}`
);
try {
await receiveComment(event.value, entry.id);
debugFacebook(
`Successfully saved ${JSON.stringify(event.value)}`
);
res.end('success');
} catch (e) {
debugError(`Error processing comment: ${e.message}`);
res.end('success');
}
}
if (FACEBOOK_POST_TYPES.includes(event.value.item)) {
try {
debugFacebook(
`Received post data ${JSON.stringify(event.value)}`
);
await receivePost(event.value, entry.id);
debugFacebook(
`Successfully saved post ${JSON.stringify(event.value)}`
);
res.end('success');
} catch (e) {
debugError(`Error processing comment: ${e.message}`);
res.end('success');
}
} else {
res.end('success');
}
}
}
}
});
};
export default init;