Created
November 21, 2019 00:14
Star
You must be signed in to star a gist
Super Fan Reviews Worker Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const Twitter = require('twitter'); | |
const config = require('./config.js'); | |
const database = require('./firebase.js'); | |
const censor = new (require('censor-sensor').CensorSensor)(); | |
const axios = require('axios'); | |
// Allow common profanity. | |
censor.disableTier(2); | |
// Initialise Twitter client. | |
var T = new Twitter(config); | |
const TWITTER_CHARACTER_LIMIT = 280; | |
// 4.5 hours | |
const POST_CADENCE = 16200000; | |
const NUM_PREVIOUS_REVIEWS = 8; | |
const NUM_PREVIOUS_REVIEWS_BACKOFF = 20; | |
const FREE_TEXT = "\n\nProduct received for free"; | |
const EARLY_TEXT = "\n\nWritten during early access"; | |
/** | |
* Retrieve the gameIds of the last x reviews posted to Twitter. | |
* | |
* @param num x | |
*/ | |
async function getPreviousReviewGameIds(num) { | |
return database.ref('posted/').orderByKey().limitToLast(num).once('value').then((snapshot) => { | |
return Object.values(snapshot.val()).map(review => review.gameId); | |
}); | |
} | |
/** | |
* Retrieve a random review from the queue. | |
*/ | |
async function getRandomReviewFromQueue(previousReviewGameIds) { | |
// Get most up-to-date settings for app. | |
const settings = await database.ref('settings/').once('value').then(snapshot => { | |
return snapshot.val() || {}; | |
}); | |
return database.ref('queue/').once('value').then(snapshot => { | |
const data = snapshot.val(); | |
let pushIds = []; | |
if (settings.usePinned) { | |
const filteredData = Object.keys(data).filter(reviewKey => data[reviewKey].pinned); | |
if (filteredData.length > 0) { | |
pushIds = filteredData; | |
} else { | |
pushIds = Object.keys(data); | |
} | |
} else { | |
pushIds = Object.keys(data); | |
} | |
if (pushIds && pushIds.length > 0) { | |
let review; | |
let randomPushId; | |
let counter = 0; | |
do { | |
const randomIndexInBounds = Math.floor(Math.random()*pushIds.length); | |
randomPushId = pushIds[randomIndexInBounds]; | |
review = data[randomPushId]; | |
counter++; | |
} while (previousReviewGameIds.includes(review. | |
gameId) && counter < NUM_PREVIOUS_REVIEWS_BACKOFF); | |
review.pushId = randomPushId; | |
return review; | |
} else { | |
return -1; | |
} | |
}); | |
} | |
/** | |
* Retrieve game review is based on. | |
*/ | |
async function getGame(review) { | |
return database.ref(`games/${review.gameId}`).once('value').then(snapshot => snapshot.val()); | |
} | |
/** | |
* Delete a specific review from the queue after it has been posted. | |
* | |
* @param {} review to delete | |
*/ | |
async function deleteReviewFromQueue(review) { | |
database.ref(`queue/${review.pushId}`).remove(); | |
} | |
/** | |
* Turn review into post-able string. | |
* | |
* @param {} review to format | |
* @param {} game review is for | |
*/ | |
function formatReviewText(review, game) { | |
const censoredText = censor.cleanProfanity(review.text); | |
let reviewText = `"${censoredText}"\n\nOn '${review.gameName}' with ${review.hoursPlayed.toLocaleString()} hrs played (avg. ${Math.floor(game.averagePlaytimeForever / 60).toLocaleString()} hrs)`; | |
if (review.freebie && reviewText.length + FREE_TEXT.length <= TWITTER_CHARACTER_LIMIT) { | |
reviewText += FREE_TEXT; | |
} else if (review.early && reviewText.length + EARLY_TEXT.length <= TWITTER_CHARACTER_LIMIT) { | |
reviewText += EARLY_TEXT; | |
} | |
return reviewText; | |
} | |
/** | |
* Get image to post alongside Tweet. | |
* | |
* @param {} review to find image for | |
*/ | |
async function getImage(review) { | |
const url = `https://steamcdn-a.akamaihd.net/steam/apps/${review.gameId}/header.jpg`; | |
const image = await axios.get(url, {responseType: 'arraybuffer'}); | |
return returned = Buffer.from(image.data); | |
} | |
/** | |
* Log that the review has been posted to prevent duplicates. | |
* | |
* @param {} review the review that has been posted | |
*/ | |
async function addReviewToPosted(review) { | |
return database.ref('posted/').push(review).catch(console.log); | |
} | |
/** | |
* Post review of game to Twitter, filtering and correlating information. | |
* | |
* @param {} review to post | |
* @param {} game review is for | |
*/ | |
async function postReview(review, game) { | |
console.log('Posting a review'); | |
const text = formatReviewText(review, game); | |
if (text > TWITTER_CHARACTER_LIMIT) { | |
console.log("Skipping as over character limit:"); | |
return -1; | |
} | |
const image = await getImage(review); | |
const media = await T.post('media/upload', { media: image }).then(media => media).catch(error => console.log); | |
if (!media) { | |
return -1; | |
} | |
const tweet = await T.post('statuses/update', { status: text, media_ids: media.media_id_string }).catch(error => console.log); | |
await deleteReviewFromQueue(review); | |
await addReviewToPosted(review); | |
return tweet; | |
} | |
/** | |
* Processing loop - let there be tweets! | |
*/ | |
async function step() { | |
const previousReviewGameIds = await getPreviousReviewGameIds(NUM_PREVIOUS_REVIEWS); | |
const review = await getRandomReviewFromQueue(previousReviewGameIds); | |
if (!review) { | |
console.log('Could not find review.'); | |
return; | |
} | |
const game = await getGame(review); | |
let outcome = -1; | |
do { | |
outcome = await postReview(review, game); | |
console.log(outcome); | |
} while (outcome === -1); | |
} | |
setInterval(step, POST_CADENCE); | |
step(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment