Skip to content

Instantly share code, notes, and snippets.

@joshhills
Created November 21, 2019 00:14
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save joshhills/87defda51ab1588461778a4a4c69c1ea to your computer and use it in GitHub Desktop.
Super Fan Reviews Worker Script
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