Skip to content

Instantly share code, notes, and snippets.

@worst-developer
Created January 23, 2017 12:50
Show Gist options
  • Save worst-developer/37f5b2c791d34802be92f6f28f0cfad1 to your computer and use it in GitHub Desktop.
Save worst-developer/37f5b2c791d34802be92f6f28f0cfad1 to your computer and use it in GitHub Desktop.
const Promise = require('bluebird');
const ObjectId = require('sails-mongo/node_modules/mongodb').ObjectID;
/**
* Advertises a video.
* Creates adset, adcreatives, ads and links it to existing compaign and account
*
* @param {string} postMessage in the ad post
* @param {object} place {lat, lng, radius}
* @param {object} time {startTime, endTime} like 2015-12-16 22:59:59 UTC or Unix time
* @param {json} boostingParams object:
* @param {integer} boostingParams.budget amount in cents
* @param {string} boostingParams.callToActionUrl
* @param {string} boostingParams.gender
* @param {array} boostingParams.age Like [minAge, maxAge],
* @param {string} videoId id of video
* @param {string} published flag indicates post visibility
* @param {string} facebookPageId id of facebook page
* @param {string} pageToken fb page token
* @param {string} userId id of the user
*/
module.exports.advertise = function (postMessage, place, time, boostingParams, videoId, published, facebookPageId, pageToken, userId) {
return new Promise(function (resolve, reject) {
let fbAdId;
let video;
let user;
const dataToSave = {
postMessage: postMessage,
budget: parseInt(boostingParams.budget),
currency: boostingParams.currency,
callToAction: boostingParams.callToActionUrl,
gender: boostingParams.gender,
age: boostingParams.age,
place: [
{
'latitude': place.lat,
'longitude': place.lng,
'radius': parseInt(place.radius),
'distance_unit': 'kilometer'
}
],
startTime: time.startTime,
endTime: time.endTime,
published: published,
pageToken: pageToken,
pageId: facebookPageId,
userId: new ObjectId(userId),
videoId: new ObjectId(videoId)
};
const FBService = FacebookService(
sails.config.shuuttv.facebook.userToken,
pageToken
);
FacebookAd.create(dataToSave).then(function (fbAd) {
fbAdId = fbAd.id;
// in order to not wait we just return id
// that can be used to obtain boosting status
resolve({ fbAdId: fbAdId });
return getUserAndVideo(videoId);
}).then((res) => {
user = res[0];
dataToSave.videoOwnerId = user.id;
video = res[1];
dataToSave.chargeBudget = video.budget;
}).then(() => {
return maybeCreateAdAccount(user);
}).then((facebookAdAccountId) => {
dataToSave.accountId = facebookAdAccountId;
// return FBService.createCampaign(
// postMessage.substr(0, 20) + ", fbAdId " + fbAdId,
// dataToSave.accountId,
// "VIDEO_VIEWS",
// process.env.NODE_ENV === 'production' ? "ACTIVE" : "PAUSED"
// );
}).then((res) => {
// dataToSave.compaignId = res.id;
dataToSave.compaignId = 6035078288133;
console.log(
"Choosing payment method (amoutOfFreeBoostings, userId):",
user.amoutOfFreeBoostings,
video.userId
);
if (user.amoutOfFreeBoostings > 0) {
console.log("Decrease amoutOfFreeBoostings");
dataToSave.isFreeBoosting = true;
return UserService.decreaseAmountOfFreeBoostings(user.id);
} else {
return PaymentService.auth(dataToSave.chargeBudget, user.id, dataToSave.currency, fbAdId).then(function (payment) {
dataToSave.paymentId = new ObjectId(payment.id);
});
}
}).then(function () {
return FBService.createFacebookAdPost(
userId,
published,
dataToSave.pageId,
postMessage,
dataToSave.callToAction,
RouteService.url('cloudfront', { name: video.path }),
video.thumbnail
);
}).then(function (fbAdPost) {
dataToSave.postId = fbAdPost.id;
return tryToCreateAdset(
parseInt(process.env.AMOUNT_OF_BOOSTING_TRIES),
fbAdId,
dataToSave.place[0].radius,
dataToSave,
FBService
);
}).then(function (adSetRes) {
dataToSave.adsetId = adSetRes.id;
return FBService.createAdcreatives(
'Creative ' + postMessage,
dataToSave.accountId,
dataToSave.pageId,
dataToSave.postId,
dataToSave.callToAction,
RouteService.url('cloudfront', { name: video.thumbnail }),
postMessage
);
}).then(function (adCreativeRes) {
dataToSave.adcreativeId = adCreativeRes.id;
return FBService.createAds(
'Ad ' + postMessage,
dataToSave.accountId,
dataToSave.adsetId,
dataToSave.adcreativeId
);
}).then(function (adsRes) {
dataToSave.adId = adsRes.id;
dataToSave.status = "sent";
delete dataToSave.userId;
delete dataToSave.videoId;
}).then(function () {
return handleSuccessfulBoosting(fbAdId, dataToSave, videoId);
}).catch(function (err) {
dataToSave.originalError = err;
dataToSave.error = [{ message: UtilsService.fetchError(err) }];
return handleFailedBoosting(
fbAdId,
dataToSave,
user.apnsToken,
videoId,
user.lang,
err
);
});
});
};
function getUserAndVideo(videoId) {
return Promise.all([
VideoV2Service.getUserByVideoId(videoId),
Video.findOne({ id: videoId })
]);
};
/*
* Creates an ad account if a user does not have it yet
* Assigns sysuser to just created an ad account
* Assigns ad account to instagram account
*/
function maybeCreateAdAccount(user) {
return "act_1027924497228108";
if (!user.facebookAdAccountId) {
const FBService = FacebookService(
sails.config.shuuttv.facebook.userToken
);
let facebookAdAccountId;
console.log("Creating an ad account", user.id);
return FBService.createAdAccount(
sails.config.shuuttv.facebook.businessId,
user.email + ", " + user.id,
sails.config.shuuttv.facebook.appID,
sails.config.shuuttv.facebook.fundingId
).then((res) => {
return Users.update(user.id, {facebookAdAccountId: res.id});
}).then((users) => {
facebookAdAccountId = users[0].facebookAdAccountId;
return FBService.updateUserPermissions(
facebookAdAccountId,
sails.config.shuuttv.facebook.userId,
sails.config.shuuttv.facebook.businessId,
"ADMIN"
);
}).then(()=>{
return FBService.assignAdAccountToInstagramAccount(
sails.config.shuuttv.facebook.instagramId,
facebookAdAccountId.replace("act_", ""),
sails.config.shuuttv.facebook.businessId
);
}).then(()=>{
return facebookAdAccountId;
})
}
return user.facebookAdAccountId;
};
function tryToCreateAdset(triesLeft, fbAdId, radius, dataToSave, FacebookService) {
dataToSave.triesToBoost = process.env.AMOUNT_OF_BOOSTING_TRIES - triesLeft;
dataToSave.place[0].radius = parseInt(radius);
return FacebookService.createAdsets(
'Budget ' + (dataToSave.budget / 100) + ", Radius " + (radius),
dataToSave.accountId,
dataToSave.compaignId,
{
budget: dataToSave.budget,
place: dataToSave.place,
gender: dataToSave.gender,
age: dataToSave.age,
place: dataToSave.place,
},
{
startTime: dataToSave.startTime,
endTime: dataToSave.endTime,
},
'VIDEO_VIEWS',
'ACTIVE',
'VIDEO_VIEWS'
).catch(function (err) {
if (err.error_user_title === "Radius too small" && triesLeft > 0) {
var newRadius = parseInt(dataToSave.place[0].radius) + 4;
console.log("Trying again with", newRadius);
console.log("Tries left", triesLeft);
return tryToCreateAdset(triesLeft - 1, fbAdId, newRadius, dataToSave, FacebookService);
};
return MarketingService.cleanUp(dataToSave.postId, dataToSave.pageToken).then(function (status) {
console.log("Cleaned UP", status);
}).then(function () {
return Promise.reject(err);
});
});
}
function handleSuccessfulBoosting(fbAdId, dataToSave, videoId) {
return Promise.all([
FacebookAd.updateNative({ _id: ObjectId(fbAdId) }, dataToSave),
VideoV2Service.updateBoostStatus(videoId, 'SCHEDULED_FOR_REVIEW')
]).then(() => {
console.log('Boosting Succeeded');
console.log('Boost status changed to SCHEDULED_FOR_REVIEW');
});
};
function handleFailedBoosting(fbAdId, dataToSave, apns, videoId, lang, err) {
let handle = [
FacebookAd.updateNative({ _id: ObjectId(fbAdId) }, dataToSave)
];
if (dataToSave.isFreeBoosting) {
console.log("Restore free boosting amount");
handle.push(
UserService.increaseAmountOfFreeBoostings(dataToSave.videoOwnerId)
);
}
return Promise.all(handle).then((result) => {
sails.log.error(err);
console.log(result[0].result.nModified ? "FacebookAd error Saved" : "FacebookAd error not Saved");
});
};
/**
* Revert changes
* @param {string} videoId id of video
*/
module.exports.cleanUp = function (videoId, pageToken) {
return FacebookService(
sails.config.shuuttv.facebook.userToken,
pageToken
).removeVideoPost(videoId);
};
/**
* Status of boosted ad
* @param {string} videoId id of video
*/
module.exports.status = function (id) {
return FacebookAd.findNative({ _id: new ObjectId(id) }).then((r) => {
return r[0];
});
};
/**
* Handle getting permissions to the page
* It will either:
* try to claim access to the Page if our System User does not have role yet
* Or try to assign permissions to our System User if user has already approved claimed access
* @param {string} pageId id of page
*/
module.exports.getAccessToPage = function (pageId, pageName, userId) {
let res = {
hasAccessToThePage: false,
isClaimAccessToPagePending: false,
changingPageRoleUrl: "https://business.facebook.com/"+pageId+"/settings/?tab=admin_roles"
};
let FBService = FacebookService(sails.config.shuuttv.facebook.userToken);
let savePageId = (userId, pageName, pageId) => {
return Users.update({id: userId}, {facebookPageName: pageName, facebookPageId: pageId});
};
let maybeAssignSystemUserToPage = (pageId) => {
return this.checkPermissionsToPage(pageId)
.then((hasRoleOnPage) => {
if (!hasRoleOnPage) {
console.log("Trying to assing permission to page", pageId);
return FBService.updateUserPermissions(
pageId,
sails.config.shuuttv.facebook.userId,
sails.config.shuuttv.facebook.businessId
);
}
return hasRoleOnPage;
}).then(()=>{
return savePageId(userId, pageName, pageId);
}).then(()=>{
return true;
});
};
if (pageId == sails.config.shuuttv.facebook.pageId) {
res.hasAccessToThePage = true;
console.log("User choose to use our fb page", userId);
return savePageId(userId, pageName, pageId).then((result)=>{
return res;
});
}
return FBService.getPageAgencies(pageId)
.then((agencies) => {
return agencies.data.filter((agency) => {
return agency.id === sails.config.shuuttv.facebook.businessId;
})[0];
})
.then((agency) => {
if (!agency) {
console.log("Will claim access");
res.isClaimAccessToPagePending = true;
return this.claimAccessToPage(pageId);
} else if(agency.access_status === 'CONFIRMED') {
console.log("Access already confirmed");
return maybeAssignSystemUserToPage(pageId).then((result) => {
res.hasAccessToThePage = result;
return;
});
} else {
console.log("Claim access is pending");
res.isClaimAccessToPagePending = true;
return;
}
})
.then(() => {
return res;
});
};
/**
* Check if we have permissions to a page
* @param {string} pageId id of page
*/
module.exports.checkPermissionsToPage = function (pageId) {
return FacebookService(sails.config.shuuttv.facebook.userToken)
.getUserPermissions(pageId)
.then((results) => {
return results.data.some((item) => {
return item.user.id === sails.config.shuuttv.facebook.userId &&
item.role === "MANAGER";
});
});
};
/**
* Claims access to the page
* @param {string} pageId id of page
*/
module.exports.claimAccessToPage = function (pageId) {
return FacebookService(sails.config.shuuttv.facebook.userToken)
.claimAccessToPage(sails.config.shuuttv.facebook.businessId, pageId)
.then((result) => {
if (result.access_status === 'PENDING') {
return {succeeded: true};
} else {
console.log("Claiming access to a page is failed", result);
return {succeeded: false};
}
});
};
module.exports.getFacebookPagesWithTokens = function (videoId) {
let pages = {facebookPages: JSON.parse(process.env.FACEBOOK_ALL_PAGES)};
return Video.findOne(videoId).then((video)=>{
return Users.findOne(video.userId.toHexString());
}).then((user) => {
if (!user.facebookPageId || sails.config.shuuttv.facebook.pageId === user.facebookPageId) {
return pages;
}
return FacebookService(sails.config.shuuttv.facebook.userToken)
.getPage(user.facebookPageId)
.then((result)=>{
pages.facebookPages = [{
id: user.facebookPageId,
token: result.access_token,
name: user.facebookPageName
}];
return pages;
});
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment