Skip to content

Commit

Permalink
Merge pull request #374 from kaogeek/dev-shiorin
Browse files Browse the repository at this point in the history
Auto Post from facebook webhook
  • Loading branch information
chaluckabs authored Oct 14, 2021
2 parents ca18369 + 9a5a268 commit 567c107
Show file tree
Hide file tree
Showing 8 changed files with 376 additions and 2 deletions.
208 changes: 208 additions & 0 deletions api-spanboon/src/api/controllers/FacebookWebhookController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
/*
* @license Spanboon Platform v0.1
* (c) 2020-2021 KaoGeek. http://kaogeek.dev
* License: MIT. https://opensource.org/licenses/MIT
* Author: shiorin <[email protected]>
*/

import 'reflect-metadata';
import moment from 'moment';
import { JsonController, Res, QueryParams, Get, Body } from 'routing-controllers';
import { ObjectID } from 'mongodb';
import { PROVIDER } from '../../constants/LoginProvider';
import { PageSocialAccountService } from '../services/PageSocialAccountService';
import { FacebookWebhookLogsService } from '../services/FacebookWebhookLogsService';
import { PageService } from '../services/PageService';
import { PostsService } from '../services/PostsService';
import { SocialPostService } from '../services/SocialPostService';
import { AssetService } from '../services/AssetService';
import { PostsGalleryService } from '../services/PostsGalleryService';
import { FacebookWebhookLogs } from '../models/FacebookWebhookLogs';
import { Posts } from '../models/Posts';
import { PostsGallery } from '../models/PostsGallery';
import { SocialPost } from '../models/SocialPost';
import { POST_TYPE } from '../../constants/PostType';
import { ASSET_PATH } from '../../constants/AssetScope';
import { facebook_setup } from '../../env';

@JsonController('/fb_webhook')
export class FacebookWebhookController {
constructor(private pageSocialAccountService: PageSocialAccountService, private facebookWebhookLogsService: FacebookWebhookLogsService,
private pageService: PageService, private postsService: PostsService, private socialPostService: SocialPostService,
private assetService: AssetService, private postsGalleryService: PostsGalleryService) { }

/**
* @api {get} /api/fb_webhook/page_feeds WebHook for page feed
* @apiGroup Facebook
* @apiSuccessExample {json} Success
* HTTP/1.1 200 OK
* {
* "message": "",
* "data":{
* }
* "status": "1"
* }
* @apiSampleRequest /api/fb_webhook/page_feeds
* @apiErrorExample {json} WebHook for page feed
* HTTP/1.1 500 Internal Server Error
*/
@Get('/page_feeds')
public async verifyPageFeedWebhook(@QueryParams() params: any, @Body({ validate: true }) body: any, @Res() res: any): Promise<any> {
const VERIFY_TOKEN = facebook_setup.FACEBOOK_VERIFY_TOKEN;

// Parse the query params
const mode = params['hub.mode'];
const token = params['hub.verify_token'];
const challenge = params['hub.challenge'];

// Checks if a token and mode is in the query string of the request
if (mode && token) {
// Checks the mode and token sent is correct
if (mode === 'subscribe' && token === VERIFY_TOKEN) {

// Responds with the challenge token from the request
console.log('FACEBOOK_WEBHOOK_VERIFIED');
return res.status(200).send(challenge);
} else {
// Responds with '403 Forbidden' if verify tokens do not match
return res.sendStatus(403);
}
}

let createLog = true;
if (body !== undefined) {
if (body.object === 'page') {
if (body.entry !== undefined) {
for (let index = 0; index < body.entry.length; index++) {
const element = body.entry[index];
if (element.changes !== undefined) {
for (const item of element.changes) {
const pageId = item.value.from.id;
const fbPostId = item.value.post_id;
const message = item.value.message;
const link = item.value.link; // photo link, can be undefined
const photos = item.value.photos; // array of photo, can be undefined
// const createdTime = item.value.created_time;
// const verb = item.value.verb;

// check if contains pageSocialAccount
const pageSocialAccount = await this.pageSocialAccountService.findOne({ providerPageId: pageId, providerName: PROVIDER.FACEBOOK });
if (pageSocialAccount === undefined) {
createLog = false;
continue;
}

const spanboonPage = await this.pageService.findOne({ _id: pageSocialAccount.page, banned: false });
if (spanboonPage === undefined) {
createLog = false;
continue;
}

// check if fbPostId was post by page
const hasSocialPosted = await this.socialPostService.findOne({ pageId: pageSocialAccount.page, socialId: fbPostId, socialType: PROVIDER.FACEBOOK });
if (hasSocialPosted !== undefined) {
createLog = false;
continue;
}

// create post
let post = this.createPagePostModel(pageSocialAccount.page, spanboonPage.ownerUser, undefined, (message ? message : ''), undefined);
post = await this.postsService.create(post);

const photoGallery = [];
if (link !== undefined && link !== '') {
// this is one photo mode
try {
const asset = await this.assetService.createAssetFromURL(link, spanboonPage.ownerUser);
if (asset !== undefined) {
photoGallery.push(asset);
}
} catch (error) {
console.log('error create asset from url', error);
}
} else if (photos !== undefined && photos.length > 0) {
// this is many photo mode
for (const plink of photos) {
try {
const asset = await this.assetService.createAssetFromURL(plink, spanboonPage.ownerUser);
if (asset !== undefined) {
photoGallery.push(asset);
}
} catch (error) {
console.log('error create asset from url', error);
}
}
}

if (post !== undefined) {
if (photoGallery.length > 0) {
// create post gallery
for (const asset of photoGallery) {
const gallery = new PostsGallery();
gallery.fileId = asset.id;
gallery.post = post.id;
gallery.imageURL = asset ? ASSET_PATH + asset.id : '';
gallery.s3ImageURL = asset.s3FilePath;
gallery.ordering = 1;
await this.postsGalleryService.create(gallery);
}
}

const socialPost = new SocialPost();
socialPost.pageId = pageSocialAccount.page;
socialPost.postId = post.id;
socialPost.postBy = pageSocialAccount.page;
socialPost.postByType = 'PAGE';
socialPost.socialId = fbPostId;
socialPost.socialType = PROVIDER.FACEBOOK;

await this.socialPostService.create(socialPost);
}
}
}
}
}
}

if (createLog) {
const logs = new FacebookWebhookLogs();
logs.data = body;
this.facebookWebhookLogsService.create(logs);
}
}

return res.status(200).send(challenge);
}

private createPagePostModel(pageObjId: ObjectID, userObjId: ObjectID, title: string, detail: string, photoLinks: string[]): Posts {
const today = moment().toDate();

const post = new Posts();
post.deleted = false;
post.pageId = pageObjId;
post.referencePost = null;
post.rootReferencePost = null;
post.visibility = null;
post.ranges = null;
post.title = title;
post.detail = detail;
post.isDraft = false;
post.hidden = false;
post.type = POST_TYPE.GENERAL;
post.userTags = [];
post.coverImage = '';
post.pinned = false;
post.deleted = false;
post.ownerUser = userObjId;
post.commentCount = 0;
post.repostCount = 0;
post.shareCount = 0;
post.likeCount = 0;
post.viewCount = 0;
post.createdDate = today;
post.startDateTime = today;
post.story = null;

return post;
}
}
4 changes: 2 additions & 2 deletions api-spanboon/src/api/controllers/GuestController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ export class GuestController {

const fbUser = await this.facebookService.getFacebookUserFromToken(decryptToken.token);
user = fbUser.user;
} catch (ex) {
} catch (ex: any) {
const errorResponse: any = { status: 0, message: ex.message };
return response.status(400).send(errorResponse);
}
Expand All @@ -1066,7 +1066,7 @@ export class GuestController {

const keyMap = ObjectUtil.parseQueryParamToMap(decryptToken.token);
user = await this.twitterService.getTwitterUser(keyMap['user_id']);
} catch (ex) {
} catch (ex: any) {
const errorResponse: any = { status: 0, message: ex.message };
return response.status(400).send(errorResponse);
}
Expand Down
21 changes: 21 additions & 0 deletions api-spanboon/src/api/models/FacebookWebhookLogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Column, Entity, BeforeInsert, ObjectIdColumn, ObjectID } from 'typeorm';
import moment = require('moment/moment');
import { IsNotEmpty, IsMongoId } from 'class-validator';
import { BaseModel } from './BaseModel';

@Entity('FacebookWebhookLogs')
export class FacebookWebhookLogs extends BaseModel {

@ObjectIdColumn({ name: '_id' })
@IsNotEmpty()
@IsMongoId()
public id: ObjectID;

@Column({ name: 'data' })
public data: any;

@BeforeInsert()
public async createDetails(): Promise<void> {
this.createdDate = moment().toDate();
}
}
14 changes: 14 additions & 0 deletions api-spanboon/src/api/repositories/FacebookWebhookLogsRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* @license Spanboon Platform v0.1
* (c) 2020-2021 KaoGeek. http://kaogeek.dev
* License: MIT. https://opensource.org/licenses/MIT
* Author: shiorin <[email protected]>
*/

import { EntityRepository, MongoRepository } from 'typeorm';
import { FacebookWebhookLogs } from '../models/FacebookWebhookLogs';

@EntityRepository(FacebookWebhookLogs)
export class FacebookWebhookLogsRepository extends MongoRepository<FacebookWebhookLogs> {

}
57 changes: 57 additions & 0 deletions api-spanboon/src/api/services/AssetService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Author: shiorin <[email protected]>, chalucks <[email protected]>
*/

import * as https from 'https';
import { Service } from 'typedi';
import { OrmRepository } from 'typeorm-typedi-extensions';
import { Asset } from '../models/Asset';
Expand All @@ -15,6 +16,8 @@ import { ASSET_CONFIG_NAME, DEFAULT_ASSET_CONFIG_VALUE } from '../../constants/S
import { S3Service } from '../services/S3Service';
import { ConfigService } from '../services/ConfigService';
import { aws_setup } from '../../env';
import { ASSET_SCOPE } from '../../constants/AssetScope';
import { ObjectID } from 'mongodb';

@Service()
export class AssetService {
Expand Down Expand Up @@ -156,6 +159,60 @@ export class AssetService {
return asset;
}

public createAssetFromURL(url: string, userId: ObjectID): Promise<Asset> {
if (url === undefined || url === null || url === '') {
return Promise.resolve(undefined);
}

return new Promise((resolve, reject) => {
https.get(url, (res) => {
res.setEncoding('base64');
console.log(`createAssetFromURL STATUS: ${res.statusCode}`);

let data = '';
res.on('data', (d) => {
data += d;
});

res.on('end', () => {
if (data !== undefined && data !== '') {
const mimeType = res.headers['content-type'];
const fileName = FileUtil.renameFile();

if (mimeType === undefined) {
resolve(undefined);
return;
}

const buffData = Buffer.from(data, 'base64');
const asset: Asset = new Asset();
asset.scope = ASSET_SCOPE.PUBLIC;
asset.userId = userId;
asset.fileName = fileName;
asset.data = data;
asset.mimeType = mimeType;
asset.size = buffData.length;
asset.expirationDate = null;

this.create(asset).then((savedAsset) => {
resolve(savedAsset);
}).catch((err) => {
console.log('err: ' + err);
reject(err);
});
} else {
resolve(undefined);
}
});

}).on('error', (err) => {
// Handle error
console.log('err: ' + err);
reject(err);
}).end();
});
}

private async _isUploadToS3(): Promise<boolean> {
// s3 upload by cofig
const assetUploadToS3Cfg = await this.configService.getConfig(ASSET_CONFIG_NAME.S3_STORAGE_UPLOAD);
Expand Down
40 changes: 40 additions & 0 deletions api-spanboon/src/api/services/FacebookWebhookLogsService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* @license Spanboon Platform v0.1
* (c) 2020-2021 KaoGeek. http://kaogeek.dev
* License: MIT. https://opensource.org/licenses/MIT
* Author: shiorin <[email protected]>, chalucks <[email protected]>
*/

import { Service } from 'typedi';
import { OrmRepository } from 'typeorm-typedi-extensions';
import { FacebookWebhookLogsRepository } from '../repositories/FacebookWebhookLogsRepository';

@Service()
export class FacebookWebhookLogsService {
constructor(@OrmRepository() private fbLogRepository: FacebookWebhookLogsRepository) { }

// create actionLog
public async create(actionLog: any): Promise<any> {
return await this.fbLogRepository.save(actionLog);
}

// find one actionLog
public async findOne(actionLog: any): Promise<any> {
return await this.fbLogRepository.findOne(actionLog);
}

// find all actionLog
public async findAll(): Promise<any> {
return await this.fbLogRepository.find();
}

// edit actionLog
public async update(query: any, newValue: any): Promise<any> {
return await this.fbLogRepository.updateOne(query, newValue);
}

// delete actionLog
public async delete(query: any, options?: any): Promise<any> {
return await this.fbLogRepository.deleteOne(query, options);
}
}
1 change: 1 addition & 0 deletions api-spanboon/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export const facebook_setup = {
FACEBOOK_COOKIE: getOsEnv('FACEBOOK_COOKIE'),
FACEBOOK_XFBML: getOsEnv('FACEBOOK_XFBML'),
FACEBOOK_VERSION: getOsEnv('FACEBOOK_VERSION'),
FACEBOOK_VERIFY_TOKEN: getOsEnv('FACEBOOK_VERIFY_TOKEN'),
};

// Google Setup
Expand Down
Loading

0 comments on commit 567c107

Please sign in to comment.