Skip to content

Commit

Permalink
adding support for checking the 14U bandits site.
Browse files Browse the repository at this point in the history
  • Loading branch information
harvardpan committed Sep 12, 2024
1 parent e4e91e1 commit 435ae89
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 32 deletions.
59 changes: 44 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@ const {
uploadFileToS3,
} = require('./lib/aws');
const {init} = require('./setup');
const {retrieveApiToken, retrieveSecret} = require('./lib/vault');

function logMessage(message) {
const timestamp = moment().tz(config.display_time_zone).format('dddd, MMMM Do YYYY, h:mm:ss a');
console.log(`INFO: ${timestamp} - ${message}`);
}

async function tweetScreenshot(imageBuffer) {
async function tweetScreenshot(imageBuffer, settings) {
const twitterConsumerKey = await retrieveSecret(settings.apiToken, 'TWITTER_CONSUMER_KEY', settings.hcp_app_name);
const twitterConsumerSecret = await retrieveSecret(settings.apiToken, 'TWITTER_CONSUMER_SECRET', settings.hcp_app_name);
const twitterAccessTokenKey = await retrieveSecret(settings.apiToken, 'TWITTER_ACCESS_TOKEN_KEY', settings.hcp_app_name);
const twitterAccessTokenSecret = await retrieveSecret(settings.apiToken, 'TWITTER_ACCESS_TOKEN_SECRET', settings.hcp_app_name);

const client = new TwitterApi({
appKey: config.consumer_key,
appSecret: config.consumer_secret,
accessToken: config.access_token_key,
accessSecret: config.access_token_secret,
appKey: twitterConsumerKey,
appSecret: twitterConsumerSecret,
accessToken: twitterAccessTokenKey,
accessSecret: twitterAccessTokenSecret,
});

// First, post all your images to Twitter
Expand All @@ -41,21 +47,29 @@ async function tweetScreenshot(imageBuffer) {
const timestamp = moment().tz(config.display_time_zone).format('dddd, MMMM Do YYYY, h:mm:ss a');
// mediaIds is a string[], can be given to .tweet
await client.v2.tweet({
text: `Latest Bandits 12U Schedule as of ${timestamp}. https://www.brooklinebaseball.net/bandits12u #bandits12u`,
text: `Latest Bandits Schedule as of ${timestamp}. ${settings.url}`,
media: {media_ids: mediaIds},
});

logMessage(`Your image tweet has successfully posted`);
}

async function main() {
/**
*
* @param {Object} settings A settings objects which contains the following properties:
* - url: The URL of the Bandits site to check
* - hcp_app_name: The name of the HCP app to use for Twitter handle and secrets
* - apiToken: The API token to use for HCP Vault Secrets, passed from the global scope
* @returns
*/
async function checkBanditsSite(settings) {
const browser = await puppeteer.launch({
headless: 'new',
args: ['--no-sandbox', '--disable-setuid-sandbox'],
});
try {
const page = await browser.newPage();
await page.goto('https://www.brooklinebaseball.net/bandits12u');
await page.goto(settings.url); // Go to the specified Bandits site based on URL
// Grab the page's HTML data
const pageData = await page.evaluate(() => {
return {html: document.documentElement.innerHTML};
Expand All @@ -64,7 +78,9 @@ async function main() {
const $ = cheerio.load(pageData.html);
const scheduleNode = $('h5:contains("Upcoming Schedule")').parent(); // contains the entire schedule section
const schedule = parseSchedule(scheduleNode.text());
const scheduleDiff = await diffSchedule(schedule);
// Retrieve the Twitter secrets from HCP Vault Secrets
const twitterUserHandle = await retrieveSecret(settings.apiToken, 'TWITTER_USER_HANDLE', settings.hcp_app_name);
const scheduleDiff = await diffSchedule(schedule, twitterUserHandle);
if (!scheduleDiff.added.size && !scheduleDiff.deleted.size && !scheduleDiff.modified.size) {
// If there are no changes, then we don't need to do anything.
logMessage(`No differences detected. No need to publish notification.`);
Expand All @@ -90,23 +106,35 @@ async function main() {
},
omitBackground: true,
});

// Since a diff was detected, we want to:
// - upload the latest screenshot to the archive
// - serialize the schedule json
// - copy the schedule json to the archive
// - tweet out the latest screenshot
await uploadFileToS3(imageBuffer, `${config.twitterUserHandle}/archive/${screenshotFilenameBase}`);
await serializeSchedule(schedule, `${config.twitterUserHandle}/previousSchedule.json`);
await serializeSchedule(schedule, `${config.twitterUserHandle}/archive/${scheduleFilenameBase}`);
await tweetScreenshot(imageBuffer);
await uploadFileToS3(imageBuffer, `${twitterUserHandle}/archive/${screenshotFilenameBase}`);
await serializeSchedule(schedule, `${twitterUserHandle}/previousSchedule.json`);
await serializeSchedule(schedule, `${twitterUserHandle}/archive/${scheduleFilenameBase}`);
await tweetScreenshot(imageBuffer, settings);
} catch (e) {
logMessage('ERROR: Uncaught exception occurred');
console.log(e);
} finally {
await browser.close();
}
}
async function main(apiToken) {
await checkBanditsSite({
url: 'https://www.brooklinebaseball.net/bandits12u',
hcp_app_name: 'BanditsNotificationBot',
apiToken: apiToken
});
await checkBanditsSite({
url: 'https://www.brooklinebaseball.net/bandits14u',
hcp_app_name: 'BanditsNotification14UBot',
apiToken: apiToken
});
}

function sleep(ms) {
return new Promise((resolve) => {
Expand All @@ -116,9 +144,10 @@ function sleep(ms) {

(async () => {
await init(); // connect to HCP Vault Secrets and populate environment variables
const apiToken = await retrieveApiToken();

while (true) {
await main();
await main(apiToken);
await sleep(config.runInterval * 1000); // multiply by 1000 as sleep takes milliseconds
}
})();
16 changes: 6 additions & 10 deletions lib/helper_functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const moment = require('moment-timezone');
const chrono = require('chrono-node');
const {EJSON} = require('bson');
const config = require('../config');
const {uploadFileToS3, getFileFromS3} = require('./aws');

/**
Expand Down Expand Up @@ -93,6 +92,10 @@ async function serializeSchedule(schedule, filepath) {

async function deserializeSchedule(filepath) {
const data = await getFileFromS3(filepath);
if (!data) {
// The schedule doesn't exist yet, so we return an empty Map
return new Map();
}
const scheduleObject = EJSON.parse(data);

// Convert the Object => Map
Expand Down Expand Up @@ -124,15 +127,8 @@ function getTimestampedFilename(filenameBase = 'schedule-screenshot', extension
* @param {Map} schedule the schedule Map object that should be compared
* @return {Object} the output of comparing the schedule with the previous schedule
*/
async function diffSchedule(schedule) {
const PREVIOUS_SCHEDULE_FILENAME = `${config.twitterUserHandle}/previousSchedule.json`;
const existingSchedule = await getFileFromS3(PREVIOUS_SCHEDULE_FILENAME);
if (!existingSchedule) {
// Usually, if the previous schedule doesn't exist, this is the first
// time that this is running in the docker container. Will not need this
// logic anymore once we move the previous blobs to S3
await serializeSchedule(schedule, PREVIOUS_SCHEDULE_FILENAME);
}
async function diffSchedule(schedule, twitterUserHandle) {
const PREVIOUS_SCHEDULE_FILENAME = `${twitterUserHandle}/previousSchedule.json`;
const previousSchedule = await deserializeSchedule(PREVIOUS_SCHEDULE_FILENAME); // deserialize actually constructs the necessary schedule Map
return compareSchedules(previousSchedule, schedule);
}
Expand Down
8 changes: 6 additions & 2 deletions lib/vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,13 @@ async function retrieveApiToken() {
* @param {String} secretName the key of the secret to retrieve
* @return {String} the value stored for the secret
*/
async function retrieveSecret(apiToken, secretName) {
async function retrieveSecret(apiToken, secretName, hcp_app_name = '') {
try {
const result = await axios.get(`secrets/2023-06-13/organizations/${config.hcp_organization_id}/projects/${config.hcp_project_id}/apps/${config.hcp_application_name}/open/${secretName}`,
if (secretName.startsWith('AWS_')) {
// We override the app name with the AWS app name
hcp_app_name = 'AWS-S3-Access';
}
const result = await axios.get(`secrets/2023-06-13/organizations/${config.hcp_organization_id}/projects/${config.hcp_project_id}/apps/${hcp_app_name}/open/${secretName}`,
{
baseURL: 'https://api.cloud.hashicorp.com',
headers: {
Expand Down
6 changes: 1 addition & 5 deletions setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ const {retrieveApiToken, retrieveSecret} = require('./lib/vault');
*/
async function init() {
const apiToken = await retrieveApiToken();
process.env.TWITTER_CONSUMER_KEY = await retrieveSecret(apiToken, 'TWITTER_CONSUMER_KEY');
process.env.TWITTER_CONSUMER_SECRET = await retrieveSecret(apiToken, 'TWITTER_CONSUMER_SECRET');
process.env.TWITTER_ACCESS_TOKEN_KEY = await retrieveSecret(apiToken, 'TWITTER_ACCESS_TOKEN_KEY');
process.env.TWITTER_ACCESS_TOKEN_SECRET = await retrieveSecret(apiToken, 'TWITTER_ACCESS_TOKEN_SECRET');
process.env.TWITTER_USER_HANDLE = await retrieveSecret(apiToken, 'TWITTER_USER_HANDLE');
// The AWS S3 environment variables are retrieved from the HCP Vault "AWS-S3-Access" app
process.env.AWS_ACCESS_KEY_ID = await retrieveSecret(apiToken, 'AWS_ACCESS_KEY_ID');
process.env.AWS_SECRET_ACCESS_KEY = await retrieveSecret(apiToken, 'AWS_SECRET_ACCESS_KEY');
process.env.AWS_DEFAULT_REGION = await retrieveSecret(apiToken, 'AWS_DEFAULT_REGION');
Expand Down

0 comments on commit 435ae89

Please sign in to comment.