UNPKG

@speakr/speakr-module-services

Version:
513 lines (452 loc) • 14.7 kB
'use strict'; 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)); }