@speakr/speakr-module-services
Version:
SPEAKR Shared Service Module
513 lines (452 loc) • 14.7 kB
JavaScript
;
const fs = require('fs');
const path = require('path');
const db = require('../../db');
const mailer = require('../../utils/mailer');
const email = require('../email');
const invoiceFeature = require('../invoice');
const accountFeature = require('../account');
const statusFeature = require('../status');
const creativeRead = require('./read');
const payment = require('../payment');
const notification = require('../notification');
const Twitter = require('twitter');
const Twit = require('twit');
const request = require('request').defaults({ encoding: null });
const requestP = require('request-promise');
const moment = require('moment');
module.exports = {
update,
byIdHash,
offerPrice,
mediaByIdHash,
publish
};
let TWITTER_API_CONSUMER_KEY = '9EJnofGvSlISPZzPyGcsw';
let TWITTER_API_CONSUMER_SECRET = 'p4ARmlOShWcN2OwwozyMgmY2zSosBkXYMYB9OBLaU8';
function update(creative, config) {
return db.creatives.update(config, {where: {id: creative.id}});
}
function mediaByIdHash(options) {
//let update = options.media.config;
let where = options.req.body.from_influencer_app ? { id_hash: options.req.body.creative_id_hash } : { id_hash: options.params.creative_id_hash };
let update;
if(options.media.contentType === 'image'){
update = {
image_link: options.media.mediaUrl
}
} else {
update = {
video_link: options.media.mediaUrl
}
}
return db.creatives.update(update, { where: where})
.then(creative => {
if(!!creative) {
options.creative = creative;
return Promise.resolve(options);
}
return Promise.reject(options)
}).catch(err => {
return Promise.reject(err)
})
}
function byIdHash(options){
const id_hash = options.req.params.id_hash;
const where = { id_hash }
return db.creatives.findOne({ where })
.then(creative => {
if(!creative) throw new Error('ID_HASH could not be found');
return creative.updateAttributes(options.req.body)
.then(updatedCreative => {
options.creatives = updatedCreative;
return options;
})
})
.catch(err => Promise.reject(err))
}
function offerPrice(creative_id, amount) {
return db.creatives.update(
{ offer_price: amount },
{ where: { id: creative_id } }
)
}
function publish(creative, message) {
function publishMethod(c) {
switch(c.platform_id) {
case 1: // Twitter
return _publishTwitter(c);
case 8: // Facebook
return _publishFacebook(c);
default:
return null;
}
}
return db.accounts.findOne({
where: { id: creative.account_id },
attributes: ['access_token', 'access_secret', 'current_username', 'platform_user_id', 'influencer_id', 'platform_id', 'validated']
}).then(account => {
account = account.dataValues ? account.dataValues : account;
creative.account = account;
return creative;
}).then(creative => {
return publishMethod(creative)
.then(res => { console.log('\n\n\n\nMAIN METHOD\n\n', res);
const live_link = res.live_link;
return _afterPublishUpdates(creative, message, live_link);
})
.catch(err => {
console.log('\n error in the main publish function:', err);
return Promise.reject(err)
})
})
.catch(err => {
console.log('\n ERROR', err, '\n');
return Promise.reject(err)
});
}
function _publishTwitter(creative) {
console.log(`\nPublishing :: ${creative.id} \n`);
return new Promise((resolve, reject) => {
if (creative.platform_id !== 1) {
reject(`Creative#${creative.id} is a not Twitter creative and cannot be published.`);
return;
}
if (creative.account.platform_id !== 1) {
reject(`Account#${creative.account_id} is a not Twitter account and cannot be used.`);
return;
}
const client = new Twitter({
consumer_key: TWITTER_API_CONSUMER_KEY,
consumer_secret: TWITTER_API_CONSUMER_SECRET,
access_token_key: creative.account.access_token,
access_token_secret: creative.account.access_secret
});
if (creative.content_type === 'image') {
_twitterImagePub(creative, client, resolve, reject)
} else if (creative.content_type === 'video') {
_twitterVideoPub(creative, resolve, reject)
} else {
console.log('\n\nupdating status :: ', creative.content,'\n\n');
client.post('statuses/update', {
status: creative.content
}, function (error, tweet) {
if (error) {
console.log('Error at final client.post("statuses/update")', error);
reject(error);
} else {
console.log(tweet);
resolve({live_link: `https://twitter.com/${creative.account.current_username}/status/${tweet.id_str}`});
}
});
}
});
}
function _twitterImagePub(creative, client, resolve, reject) {
let media = creative.image_link;
console.log(`Fetching ${media}`);
request.get(media, function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(`Uploading media to Twitter`);
client.post('media/upload', {media: body}, function (error, media_id) {
if (error) {
console.log(error);
reject(error);
} else {
console.log(`Posting tweet to Twitter ${creative.content}`);
client.post('statuses/update', {
status: creative.content,
media_ids: media_id.media_id_string
}, function (error, tweet) {
if (error) {
console.log(error);
reject(error);
} else {
console.log(tweet);
resolve({live_link: `https://twitter.com/${creative.account.current_username}/status/${tweet.id_str}`});
}
});
}
});
} else {
console.log(error);
reject(error);
}
});
}
function _twitterVideoPub(creative, resolve, reject) {
const T = new Twit({
consumer_key: TWITTER_API_CONSUMER_KEY,
consumer_secret: TWITTER_API_CONSUMER_SECRET,
access_token: creative.account.access_token,
access_token_secret: creative.account.access_secret
});
const mediaUrl = creative.video_link;
let localname = mediaUrl.slice(43);
if(localname.slice(localname.length - 3).toUpperCase() === 'MOV') {
localname = localname.slice(0, localname.length - 3) + 'mp4';
}
if(localname.slice(localname.length - 3).toUpperCase() !== 'MP4') {
reject({
error: `Wrong video type.`,
message: `Twitter videos must be mp4, this is a ${localname.slice(localname.length - 3)}`
})
} else {
const PATH = path.join(__dirname, `../../../../../tmp/${localname}`);
request.get(mediaUrl, function (error, response, body) {
fs.writeFile(PATH, body, function(error) {
T.postMediaChunked({ file_path: PATH }, function (err, data, response) {
if(err) {
reject({
error: 'Problem with postMediaChunked video upload method.',
message: err.message,
purpose: err.message
});
}
const mediaIdStr = data.media_id_string;
const meta_params = { media_id: mediaIdStr };
T.post('media/metadata/create', meta_params, function (err, data, response) {
if(err) {
reject({
error: 'Problem with "media/metadata/create" Twitter video post.',
message: err.message,
purpose: err.message
});
} else if (!err) {
const params = { status: creative.content, media_ids: [mediaIdStr] };
T.post('statuses/update', params, function (err, tweet, response) {
console.log(tweet);
if(err) {
reject({
error: 'Problem with "statuses/update" Twitter video post.',
message: err.message,
purpose: err.message
});
}
fs.unlinkSync(PATH);
const base = 'https://twitter.com/';
const handle = creative.account.current_username;
const identifier = tweet.id_str;
resolve({live_link: `${base}${handle}/status/${identifier}`});
}); // end '/statuses/update'
} else {
reject({
error: 'Problem uploading Twitter video.',
message: err.message,
purpose: err.message
});
}
}); // end '/media/metadata/create'
}); // end T.postMediaChunked
}); //end fs.writeFile
}); // end request.get
}
}
function _publishFacebook(creative) {
const sponsor_id = creative.initiative.fb_sponsor_id;
const user_access_token = creative.account.access_token;
const user_fb_id = creative.account.platform_user_id;
const pageTokenOptions = {
method: 'GET',
uri: `https://graph.facebook.com/v2.8/${user_fb_id}`,
qs: {
access_token: user_access_token,
fields: 'access_token'
}
};
return requestP(pageTokenOptions)
.then(tokenRes => {
const page_access_token = JSON.parse(tokenRes).access_token;
const page_id = JSON.parse(tokenRes).id;
return _mediaTypeSwitch(creative, sponsor_id, page_access_token, page_id)
})
.then(fbRes => {
let fb_id;
let type;
if(creative.image_link) {
type = 'post';
fb_id = JSON.parse(fbRes).post_id ? JSON.parse(fbRes).post_id : JSON.parse(fbRes).id;
} else if(creative.video_link) {
type = 'video';
fb_id = JSON.parse(fbRes).id;
} else {
type = 'post';
fb_id = JSON.parse(fbRes).id;
}
const getUrlOptions = {
method: 'GET',
uri: `https://graph.facebook.com/v2.8/${fb_id}`,
qs: {
access_token: user_access_token,
type: type,
fields: 'permalink_url'
}
};
return requestP(getUrlOptions)
.then(fbUrlRes => {
let permalink = JSON.parse(fbUrlRes).permalink_url;
if(creative.content_type === 'video') {
permalink = `https://www.facebook.com${permalink}`;
}
return {live_link: permalink};
})
})
.catch(err => {
console.log('\n ERROR >> ', err)
return Promise.reject({
error: 'There was an error posting to Facebook.',
message: 'Check which page you are posting to. **No Personal Profiles',
purpose: err.message,
});
})
}
function _mediaTypeSwitch(creative, sponsor_id, page_access_token, page_id) {
return new Promise((resolve, reject) => {
switch(creative.content_type) {
case 'image':
return resolve(_postFacebookImage(creative, sponsor_id, page_access_token, page_id));
case 'video':
return resolve(_postFacebookVideo(creative, sponsor_id, page_access_token, page_id));
case 'text':
return resolve(_postFacebookMessage(creative, sponsor_id, page_access_token, page_id));
case 'link':
return resolve(_postFacebookMessage(creative, sponsor_id, page_access_token, page_id));
default:
return resolve(_postFacebookMessage(creative, sponsor_id, page_access_token, page_id));
}
})
}
function _postFacebookMessage(creative, sponsor_id, page_access_token, page_id) {
return new Promise((resolve, reject) => {
let postMessageOptions = {
method: 'POST',
uri: `https://graph.facebook.com/v2.8/${page_id}/feed`,
qs: {
access_token: page_access_token,
message: creative.content
}
};
// if there is a sponsor, use handshake feature
if(creative.account.validated && creative.initiative.fb_sponsor_id) {
postMessageOptions.qs.sponsor_id = Number(creative.initiative.fb_sponsor_id);
}
resolve(requestP(postMessageOptions));
})
}
function _postFacebookImage(creative, sponsor_id, page_access_token, page_id) {
return new Promise((resolve, reject) => {
let postOptions = {
method: 'POST',
uri: `https://graph.facebook.com/v2.8/${page_id}/photos`,
qs: {
access_token: page_access_token,
caption: creative.content,
url: creative.image_link
}
};
// if there is a sponsor, use handshake feature
if(creative.account.validated && creative.initiative.fb_sponsor_id) {
postOptions.qs.sponsor_id = Number(creative.initiative.fb_sponsor_id);
}
resolve(requestP(postOptions))
})
}
function _postFacebookVideo(creative, sponsor_id, page_access_token, page_id) {
return new Promise((resolve, reject) => {
let postOptions = {
method: 'POST',
uri: `https://graph.facebook.com/v2.8/${page_id}/videos`,
qs: {
access_token: page_access_token,
description: creative.content,
file_url: creative.video_link
}
};
// if there is a sponsor, use handshake feature
if(creative.account.validated && creative.initiative.fb_sponsor_id) {
postOptions.qs.sponsor_id = Number(creative.initiative.fb_sponsor_id);
}
resolve(requestP(postOptions));
})
}
function _afterPublishUpdates(creative, message, live_link) {
let invoice;
let isExternal;
const network_id = creative.network_id;
return update(creative, {
submission_status: 'Published',
permalink: live_link
})
.then(() => statusFeature.create.published(creative, {message: message}))
.then(() =>{
// if partner and external skip payment (invoice find/create)
return accountFeature.validate.isExternal(network_id, creative.account.influencer_id, creative.platform_id)
.then(boolean => {
isExternal = boolean;
})
})
.then(() => {
if(network_id !== 1 && isExternal) {
return null;
} else {
return invoiceFeature.read.byInfluencerId(creative.account.influencer_id, creative.network_id)
.then(invoiceData => {
invoice = invoiceData;
if(!invoice) {
return db.influencers.findOne({
where: { id: creative.account.influencer_id }
}).then(influencer => {
const { id, paypal_email } = influencer;
return invoiceFeature.create.withInfluencerData(id, paypal_email, creative.network_id)
.then(i => {
return invoice = i.dataValues ? i.dataValues : i;
});
})
} else {
return invoice;
}
})
}
})
.then(() => creativeRead.byId(creative.id))
.then(publishedCreative => {
mailer.send(email.format.submissionStatusChanged(
Object.assign(publishedCreative, {status: 'Published', message: message})
) //end email.publishedCreativeStatusChanged
); // end mailer.send
// if partner and external skip payment (line_item create)
if(network_id !== 1 && isExternal) {
return null;
} else {
return db.line_items.create({
type: 'creative',
invoice_id: invoice.id,
payable_id: publishedCreative.id,
discount: publishedCreative.discount,
amount: publishedCreative.offer_price ? publishedCreative.offer_price : 0,
note: `Created on ${moment().format('MMMM Do YYYY, h:mm:ss a')} in Submissions App`,
created_by: 'Speakr app auto publish',
network_id: publishedCreative.network_id
})
}
})
}
function _payment(options) {
return payment.create(options)
/*
* 2. Find payment for the influencer
* */
.then(options => payment.read.byId(options.id))
/*
* 3. Create payment for the influencer
* */
.then(options => notification.create.paid(options))
/*
* 4. Return options.user as the formatted response
* */
.then((options) => Promise.resolve(options))
/*
* 5. Throw errors
* */
.catch((err) => Promise.reject(err));
}