mirror of
https://github.com/misenhower/splatoon2.ink.git
synced 2026-07-15 08:00:52 -05:00
parent
5156d738ca
commit
a18a3fb920
|
|
@ -59,7 +59,7 @@
|
|||
<SalmonRunDetailsBar :schedule="currentSchedule"></SalmonRunDetailsBar>
|
||||
</div>
|
||||
|
||||
<div v-if="upcomingSchedules.length > 0">
|
||||
<div v-if="!screenshotMode && upcomingSchedules.length > 0">
|
||||
<div class="salmon-run-future">
|
||||
<div class="is-size-6 title-squid font-splatoon1">
|
||||
Soon
|
||||
|
|
|
|||
|
|
@ -1,82 +1,16 @@
|
|||
require('./bootstrap');
|
||||
const screenshots = require('./screenshots');
|
||||
const Twitter = require('twitter');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { canTweet } = require('./twitter/client');
|
||||
const ScheduleTweet = require('./twitter/ScheduleTweet');
|
||||
const GearTweet = require('./twitter/GearTweet');
|
||||
const SalmonRunTweet = require('./twitter/SalmonRunTweet');
|
||||
const NewWeaponTweet = require('./twitter/NewWeaponTweet');
|
||||
|
||||
const dataPath = path.resolve('public/data');
|
||||
const lastTweetTimesPath = path.resolve('twitter-lastTweetTimes.json');
|
||||
|
||||
// Twitter API parameters
|
||||
const consumer_key = process.env.TWITTER_CONSUMER_KEY;
|
||||
const consumer_secret = process.env.TWITTER_CONSUMER_SECRET;
|
||||
const access_token_key = process.env.TWITTER_ACCESS_TOKEN_KEY;
|
||||
const access_token_secret = process.env.TWITTER_ACCESS_TOKEN_SECRET;
|
||||
|
||||
// Twitter API client
|
||||
const client = new Twitter({ consumer_key, consumer_secret, access_token_key, access_token_secret });
|
||||
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
function canTweet() {
|
||||
return consumer_key && consumer_secret && access_token_key && access_token_secret;
|
||||
}
|
||||
|
||||
function getTopOfCurrentHour() {
|
||||
let date = new Date;
|
||||
date.setUTCMinutes(0);
|
||||
date.setUTCSeconds(0);
|
||||
return Math.floor(date.getTime() / 1000);
|
||||
}
|
||||
|
||||
function getLastTweetTimes() {
|
||||
if (fs.existsSync(lastTweetTimesPath))
|
||||
return JSON.parse(fs.readFileSync(lastTweetTimesPath));
|
||||
return {};
|
||||
}
|
||||
|
||||
function getLastTweetTime(key) {
|
||||
return getLastTweetTimes()[key] || 0;
|
||||
}
|
||||
|
||||
function shouldTweet(key, time) {
|
||||
return getLastTweetTime(key) < time;
|
||||
}
|
||||
|
||||
function updateLastTweetTime(key, time) {
|
||||
let lastTweetTimes = getLastTweetTimes();
|
||||
lastTweetTimes[key] = time;
|
||||
|
||||
fs.writeFileSync(lastTweetTimesPath, JSON.stringify(lastTweetTimes));
|
||||
}
|
||||
|
||||
function postMediaTweet(imageData, statusText) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Upload the image
|
||||
client.post('media/upload', { media: imageData }, (error, media, response) => {
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
let status = {
|
||||
status: statusText,
|
||||
media_ids: media.media_id_string,
|
||||
};
|
||||
|
||||
client.post('statuses/update', status, (error, tweet, response) => {
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
resolve(tweet);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter posts
|
||||
*/
|
||||
let tweets = [
|
||||
new ScheduleTweet,
|
||||
new GearTweet,
|
||||
new SalmonRunTweet,
|
||||
new NewWeaponTweet,
|
||||
];
|
||||
|
||||
async function maybePostTweets() {
|
||||
if (!canTweet()) {
|
||||
|
|
@ -84,301 +18,18 @@ async function maybePostTweets() {
|
|||
return;
|
||||
}
|
||||
|
||||
await maybePostScheduleTweet();
|
||||
await maybePostGearTweet();
|
||||
await maybePostSalmonRunTweet();
|
||||
await maybePostNewWeaponTweet();
|
||||
for (let tweet of tweets)
|
||||
await tweet.maybePostTweet();
|
||||
}
|
||||
|
||||
async function testScreenshots() {
|
||||
await testScheduleScreenshot();
|
||||
await testGearScreenshot();
|
||||
await testSalmonRunScreenshot();
|
||||
await testNewWeaponScreenshot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main schedule tweets
|
||||
*/
|
||||
|
||||
async function maybePostScheduleTweet() {
|
||||
const key = 'schedule';
|
||||
|
||||
// What time are we posting the schedule for?
|
||||
let time = getTopOfCurrentHour();
|
||||
|
||||
// Have we already posted for this time?
|
||||
if (!shouldTweet(key, time)) {
|
||||
console.info('Twitter: Schedule: Already posted for this hour');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the schedules
|
||||
let schedulesPath = `${dataPath}/schedules.json`;
|
||||
if (!fs.existsSync(schedulesPath)) {
|
||||
console.warn('Twitter: Schedule: schedules.json does not exist');
|
||||
return;
|
||||
}
|
||||
let schedules = JSON.parse(fs.readFileSync(schedulesPath));
|
||||
|
||||
// Do we have a schedule for the specified time?
|
||||
if (!schedules.regular.find(s => s.start_time == time)) {
|
||||
console.info('Twitter: Schedule: No schedule for this hour');
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything looks good, let's post a tweet
|
||||
await postScheduleTweet(time);
|
||||
console.info('Twitter: Schedule: Posted latest map schedule');
|
||||
|
||||
// Update the last tweet time
|
||||
updateLastTweetTime(key, time);
|
||||
}
|
||||
|
||||
async function testScheduleScreenshot() {
|
||||
let schedulesPath = `${dataPath}/schedules.json`;
|
||||
let schedules = JSON.parse(fs.readFileSync(schedulesPath));
|
||||
let imageData = await screenshots.captureScheduleScreenshot(schedules.regular[0].start_time);
|
||||
fs.writeFileSync('test-screenshot-schedules.png', imageData);
|
||||
console.info('Saved schedule screenshot');
|
||||
}
|
||||
|
||||
async function postScheduleTweet(startTime) {
|
||||
// Generate the image
|
||||
let imageData = await screenshots.captureScheduleScreenshot(startTime);
|
||||
|
||||
// Post the tweet
|
||||
return await postMediaTweet(imageData, 'Current Splatoon 2 map rotation');
|
||||
}
|
||||
|
||||
/**
|
||||
* Gear tweets
|
||||
*/
|
||||
|
||||
function getMerchandises() {
|
||||
let merchandisesPath = `${dataPath}/merchandises.json`;
|
||||
if (!fs.existsSync(merchandisesPath)) {
|
||||
console.warn('Twitter: Gear: merchandises.json does not exist');
|
||||
return [];
|
||||
}
|
||||
return JSON.parse(fs.readFileSync(merchandisesPath)).merchandises;
|
||||
}
|
||||
|
||||
async function maybePostGearTweet() {
|
||||
const key = 'gear';
|
||||
|
||||
// What time are we posting the schedule for?
|
||||
let time = getTopOfCurrentHour();
|
||||
|
||||
// Make sure we have merchandises
|
||||
let merchandises = getMerchandises();
|
||||
if (!merchandises.length) {
|
||||
console.info('Twitter: Gear: No merchandises');
|
||||
return;
|
||||
}
|
||||
|
||||
// This one is a little different: we only have end_times for merchandise items, so we
|
||||
// need to track the latest end_time we've posted
|
||||
let endTimes = merchandises.map(m => m.end_time);
|
||||
let lastEndTime = Math.max(...endTimes);
|
||||
|
||||
// Have we already posted for this time?
|
||||
if (!shouldTweet(key, lastEndTime)) {
|
||||
console.info('Twitter: Gear: Already posted for this hour');
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything looks good, let's post a tweet
|
||||
await postGearTweet(time, lastEndTime);
|
||||
console.info('Twitter: Gear: Posted latest SplatNet gear');
|
||||
|
||||
// Update the last tweet time
|
||||
updateLastTweetTime(key, lastEndTime);
|
||||
}
|
||||
|
||||
async function testGearScreenshot() {
|
||||
let merchandises = getMerchandises();
|
||||
let imageData = await screenshots.captureGearScreenshot(getTopOfCurrentHour(), merchandises[merchandises.length - 1].end_time);
|
||||
fs.writeFileSync('test-screenshot-gear.png', imageData);
|
||||
console.info('Saved gear screenshot');
|
||||
}
|
||||
|
||||
async function postGearTweet(startTime, endTime) {
|
||||
// Generate the image
|
||||
let imageData = await screenshots.captureGearScreenshot(startTime, endTime);
|
||||
|
||||
// Generate the text
|
||||
let gearText;
|
||||
let merchandise = getMerchandises().find(g => g.end_time == endTime);
|
||||
if (!merchandise)
|
||||
return;
|
||||
gearText = `Up now on SplatNet: ${merchandise.gear.name} with ${merchandise.skill.name} #splatnet2`;
|
||||
|
||||
// Post the tweet
|
||||
return await postMediaTweet(imageData, gearText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Salmon Run tweets
|
||||
*/
|
||||
|
||||
function getSalmonRunSchedules() {
|
||||
let timelinePath = `${dataPath}/timeline.json`;
|
||||
let calendarPath = `${dataPath}/salmonruncalendar.json`;
|
||||
let schedules = {};
|
||||
|
||||
if (fs.existsSync(timelinePath)) {
|
||||
let timeline = JSON.parse(fs.readFileSync(timelinePath)).coop;
|
||||
if (timeline)
|
||||
schedules[timeline.start_time] = timeline;
|
||||
}
|
||||
|
||||
if (fs.existsSync(calendarPath)) {
|
||||
let calendar = JSON.parse(fs.readFileSync(calendarPath)).schedules;
|
||||
if (calendar) {
|
||||
for (schedule of calendar)
|
||||
schedules[schedule.start_time] = schedule;
|
||||
}
|
||||
}
|
||||
|
||||
return Object.values(schedules);
|
||||
}
|
||||
|
||||
async function maybePostSalmonRunTweet() {
|
||||
const key = 'salmonrun';
|
||||
|
||||
// What time are we posting the schedule for?
|
||||
let time = getTopOfCurrentHour();
|
||||
|
||||
// Have we already posted for this time?
|
||||
if (!shouldTweet(key, time)) {
|
||||
console.info('Twitter: Salmon Run: Already posted for this hour');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the schedules
|
||||
let schedules = getSalmonRunSchedules();
|
||||
if (!schedules.length) {
|
||||
console.warn('Twitter: Salmon Run: no schedules found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we have a schedule for the specified time?
|
||||
if (!schedules.find(s => s.start_time == time)) {
|
||||
console.info('Twitter: Salmon Run: No schedule for this hour');
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything looks good, let's post a tweet
|
||||
await postSalmonRunTweet(time);
|
||||
console.info('Twitter: Salmon Run: Posted Salmon Run schedule');
|
||||
|
||||
// Update the last tweet time
|
||||
updateLastTweetTime(key, time);
|
||||
}
|
||||
|
||||
async function testSalmonRunScreenshot() {
|
||||
let schedules = getSalmonRunSchedules();
|
||||
let imageData = await screenshots.captureSalmonRunScreenshot(schedules[0].start_time);
|
||||
fs.writeFileSync('test-screenshot-salmonrun.png', imageData);
|
||||
console.info('Saved Salmon Run screenshot');
|
||||
}
|
||||
|
||||
async function postSalmonRunTweet(startTime) {
|
||||
// Generate the image
|
||||
let imageData = await screenshots.captureSalmonRunScreenshot(startTime);
|
||||
|
||||
// Post the tweet
|
||||
return await postMediaTweet(imageData, 'Salmon Run is now open! #salmonrun');
|
||||
}
|
||||
|
||||
/**
|
||||
* New Weapon tweets
|
||||
*/
|
||||
|
||||
function getNewWeaponAvailabilities(releaseTime = null) {
|
||||
let timelinePath = `${dataPath}/timeline.json`;
|
||||
if (!fs.existsSync(timelinePath)) {
|
||||
console.warn('Twitter: Gear: timeline.json does not exist');
|
||||
return;
|
||||
}
|
||||
let weaponAvailability = JSON.parse(fs.readFileSync(timelinePath)).weapon_availability;
|
||||
if (weaponAvailability && weaponAvailability.availabilities) {
|
||||
if (releaseTime === null)
|
||||
return weaponAvailability.availabilities;
|
||||
|
||||
return weaponAvailability.availabilities.filter(a => a.release_time == releaseTime);
|
||||
}
|
||||
}
|
||||
|
||||
async function maybePostNewWeaponTweet() {
|
||||
const key = 'weapon';
|
||||
|
||||
// What time are we posting the schedule for?
|
||||
let time = getTopOfCurrentHour();
|
||||
|
||||
// Do we have a weapon?
|
||||
let weaponAvailabilities = getNewWeaponAvailabilities(time);
|
||||
if (!weaponAvailabilities || weaponAvailabilities.length == 0) {
|
||||
console.info('Twitter: No new weapon for this time');
|
||||
return;
|
||||
}
|
||||
|
||||
// Have we already posted for this time?
|
||||
if (!shouldTweet(key, time)) {
|
||||
console.info('Twitter: Weapons: Already posted for this hour');
|
||||
return;
|
||||
}
|
||||
|
||||
// Everything looks good, let's post a tweet
|
||||
await postNewWeaponTweet(time);
|
||||
console.info('Twitter: Weapons: Posted new weapon');
|
||||
|
||||
// Update the last tweet time
|
||||
updateLastTweetTime(key, time);
|
||||
}
|
||||
|
||||
async function testNewWeaponScreenshot() {
|
||||
let availabilities = getNewWeaponAvailabilities();
|
||||
let availability = availabilities[0];
|
||||
if (!availability) {
|
||||
console.info('No new weapon');
|
||||
return;
|
||||
}
|
||||
|
||||
let imageData = await screenshots.captureNewWeaponScreenshot(availability.release_time);
|
||||
fs.writeFileSync('test-screenshot-weapon.png', imageData);
|
||||
console.info('Saved new weapon screenshot');
|
||||
}
|
||||
|
||||
async function postNewWeaponTweet(releaseTime) {
|
||||
// Generate the image
|
||||
let imageData = await screenshots.captureNewWeaponScreenshot(releaseTime);
|
||||
|
||||
// Generate the text
|
||||
let availabilities = getNewWeaponAvailabilities(releaseTime);
|
||||
let tweetText;
|
||||
if (availabilities.length == 1) {
|
||||
tweetText = `New weapon, now available: ${availabilities[0].weapon.name} #splatoon2`;
|
||||
}
|
||||
else {
|
||||
tweetText = `New weapons, now available:`;
|
||||
for (availability of availabilities)
|
||||
tweetText += `\n- ${availability.weapon.name}`;
|
||||
tweetText += '\n#splatoon2';
|
||||
}
|
||||
|
||||
// Post the tweet
|
||||
return await postMediaTweet(imageData, tweetText);
|
||||
for (let tweet of tweets)
|
||||
await tweet.saveTestScreenshot();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
maybePostTweets,
|
||||
testScreenshots,
|
||||
postScheduleTweet,
|
||||
postGearTweet,
|
||||
postSalmonRunTweet,
|
||||
postNewWeaponTweet,
|
||||
}
|
||||
|
||||
require('make-runnable/custom')({ printOutputFrame: false });
|
||||
|
|
|
|||
40
src/updater/twitter/GearTweet.js
Normal file
40
src/updater/twitter/GearTweet.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
const TwitterPostBase = require('./TwitterPostBase');
|
||||
const { captureGearScreenshot } = require('../screenshots');
|
||||
const { readData, getTopOfCurrentHour } = require('./utilities');
|
||||
|
||||
class GearTweet extends TwitterPostBase {
|
||||
getKey() { return 'gear'; }
|
||||
getName() { return 'Gear'; }
|
||||
|
||||
getMerchandises() {
|
||||
return readData('merchandises.json').merchandises;
|
||||
}
|
||||
|
||||
getDataTime() {
|
||||
// We only have end_times for merchandise items, so we need to track the latest end_time
|
||||
let endTimes = this.getMerchandises().map(m => m.end_time);
|
||||
let lastEndTime = Math.max(...endTimes);
|
||||
return lastEndTime;
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.getMerchandises().find(m => m.end_time == this.getDataTime());
|
||||
}
|
||||
|
||||
getTestData() {
|
||||
let merchandises = this.getMerchandises();
|
||||
return merchandises[merchandises.length - 1];
|
||||
}
|
||||
|
||||
getImage(data) {
|
||||
let startTime = getTopOfCurrentHour();
|
||||
let endTime = this.getDataTime();
|
||||
return captureGearScreenshot(startTime, endTime);
|
||||
}
|
||||
|
||||
getText(data) {
|
||||
return `Up now on SplatNet: ${data.gear.name} with ${data.skill.name} #splatnet2`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GearTweet;
|
||||
50
src/updater/twitter/NewWeaponTweet.js
Normal file
50
src/updater/twitter/NewWeaponTweet.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
const TwitterPostBase = require('./TwitterPostBase');
|
||||
const { captureNewWeaponScreenshot } = require('../screenshots');
|
||||
const { readData } = require('./utilities');
|
||||
|
||||
class NewWeaponTweet extends TwitterPostBase {
|
||||
getKey() { return 'weapon'; }
|
||||
getName() { return 'New Weapon'; }
|
||||
|
||||
getNewWeaponAvailabilities() {
|
||||
let weaponAvailability = readData('timeline.json').weapon_availability;
|
||||
if (weaponAvailability)
|
||||
return weaponAvailability.availabilities;
|
||||
return [];
|
||||
}
|
||||
|
||||
getData() {
|
||||
let availabilities = this.getNewWeaponAvailabilities().filter(a => a.release_time == this.getDataTime());
|
||||
|
||||
// Only return the array if it contains availabilities.
|
||||
// Otherwise return false (i.e., there's no data, so don't post a Tweet).
|
||||
if (!availabilities.length)
|
||||
return false;
|
||||
|
||||
return availabilities;
|
||||
}
|
||||
|
||||
getTestData() {
|
||||
let availabilities = this.getNewWeaponAvailabilities()
|
||||
if (availabilities.length)
|
||||
return availabilities;
|
||||
}
|
||||
|
||||
getImage(data) {
|
||||
return captureNewWeaponScreenshot(data[0].release_time);
|
||||
}
|
||||
|
||||
getText(data) {
|
||||
if (data.length == 1)
|
||||
return `New weapon now available: ${data[0].weapon.name} #splatoon2`;
|
||||
|
||||
let text = `New weapons now available:`;
|
||||
for (let availability of data)
|
||||
text += `\n- ${availability.weapon.name}`;
|
||||
text += '\n#splatoon2';
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NewWeaponTweet;
|
||||
42
src/updater/twitter/SalmonRunTweet.js
Normal file
42
src/updater/twitter/SalmonRunTweet.js
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
const TwitterPostBase = require('./TwitterPostBase');
|
||||
const { captureSalmonRunScreenshot } = require('../screenshots');
|
||||
const { readData } = require('./utilities');
|
||||
|
||||
class SalmonRunTweet extends TwitterPostBase {
|
||||
getKey() { return 'salmonrun'; }
|
||||
getName() { return 'Salmon Run'; }
|
||||
|
||||
getSalmonRunSchedules() {
|
||||
let schedules = {};
|
||||
|
||||
let timeline = readData('timeline.json').coop;
|
||||
if (timeline)
|
||||
schedules[timeline.start_time] = timeline;
|
||||
|
||||
let calendar = readData('salmonruncalendar.json').schedules;
|
||||
if (calendar) {
|
||||
for (let schedule of calendar)
|
||||
schedules[schedule.start_time] = schedule;
|
||||
}
|
||||
|
||||
return Object.values(schedules);
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.getSalmonRunSchedules().find(s => s.start_time == this.getDataTime());
|
||||
}
|
||||
|
||||
getTestData() {
|
||||
return this.getSalmonRunSchedules()[0];
|
||||
}
|
||||
|
||||
getImage(data) {
|
||||
return captureSalmonRunScreenshot(data.start_time);
|
||||
}
|
||||
|
||||
getText(data) {
|
||||
return 'Salmon Run is now open! #salmonrun #splatoon2';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SalmonRunTweet;
|
||||
30
src/updater/twitter/ScheduleTweet.js
Normal file
30
src/updater/twitter/ScheduleTweet.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
const TwitterPostBase = require('./TwitterPostBase');
|
||||
const { captureScheduleScreenshot } = require('../screenshots');
|
||||
const { readData } = require('./utilities');
|
||||
|
||||
class ScheduleTweet extends TwitterPostBase {
|
||||
getKey() { return 'schedule'; }
|
||||
getName() { return 'Schedule'; }
|
||||
|
||||
getSchedules() {
|
||||
return readData('schedules.json');
|
||||
}
|
||||
|
||||
getData() {
|
||||
return this.getSchedules().regular.find(s => s.start_time == this.getDataTime());
|
||||
}
|
||||
|
||||
getTestData() {
|
||||
return this.getSchedules().regular[0];
|
||||
}
|
||||
|
||||
getImage(data) {
|
||||
return captureScheduleScreenshot(data.start_time);
|
||||
}
|
||||
|
||||
getText(data) {
|
||||
return 'Current Splatoon 2 map rotation';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ScheduleTweet;
|
||||
154
src/updater/twitter/TwitterPostBase.js
Normal file
154
src/updater/twitter/TwitterPostBase.js
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { postMediaTweet } = require('./client');
|
||||
const { getTopOfCurrentHour, readJson } = require('./utilities');
|
||||
|
||||
const lastTweetTimesPath = path.resolve('twitter-lastTweetTimes.json');
|
||||
|
||||
class TwitterPostBase {
|
||||
maybePostTweet() {
|
||||
// Make sure we have data to post
|
||||
if (!this.getData()) {
|
||||
this.info('No data to post');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.shouldPostForCurrentTime()) {
|
||||
this.info('Already posted for this time');
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.postTweet();
|
||||
}
|
||||
|
||||
async postTweet() {
|
||||
try {
|
||||
// Get the Tweet's text and image
|
||||
let data = this.getData();
|
||||
let text = await this.getText(data);
|
||||
let image = await this.getImage(data);
|
||||
|
||||
// Post to Twitter
|
||||
let tweet = await postMediaTweet(text, image);
|
||||
|
||||
// Update the last post time
|
||||
this.updateLastTweetTime();
|
||||
|
||||
this.info('Posted Tweet');
|
||||
}
|
||||
catch (e) {
|
||||
this.error('Couldn\'t post Tweet');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async saveTestScreenshot() {
|
||||
try {
|
||||
let data = this.getTestData();
|
||||
if (!data) {
|
||||
this.info('No data available');
|
||||
return;
|
||||
}
|
||||
|
||||
let filename = this.getTestScreenshotFilename();
|
||||
let image = await this.getImage(data);
|
||||
|
||||
fs.writeFileSync(filename, image);
|
||||
this.info('Saved screenshot')
|
||||
}
|
||||
catch (e) {
|
||||
this.error('Couldn\'t save screenshot');
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Post time helpers
|
||||
*/
|
||||
|
||||
getLastTweetTimes() {
|
||||
if (fs.existsSync(lastTweetTimesPath))
|
||||
return readJson(lastTweetTimesPath);
|
||||
return {};
|
||||
}
|
||||
|
||||
getLastTweetTime() {
|
||||
let key = this.getKey();
|
||||
return this.getLastTweetTimes()[key] || 0;
|
||||
}
|
||||
|
||||
updateLastTweetTime() {
|
||||
let key = this.getKey();
|
||||
let time = this.getDataTime();
|
||||
let lastTweetTimes = this.getLastTweetTimes();
|
||||
|
||||
lastTweetTimes[key] = time;
|
||||
|
||||
fs.writeFileSync(lastTweetTimesPath, JSON.stringify(lastTweetTimes));
|
||||
}
|
||||
|
||||
shouldPostForCurrentTime() {
|
||||
// Check whether the current data time has already been posted
|
||||
let time = this.getDataTime();
|
||||
let lastTweetTime = this.getLastTweetTime();
|
||||
return lastTweetTime < time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log helpers
|
||||
*/
|
||||
|
||||
formatLogMessage(message) {
|
||||
let name = this.getName();
|
||||
return `[Twitter] [${name}] ${message}`;
|
||||
}
|
||||
|
||||
log(message) {
|
||||
console.log(this.formatLogMessage(message));
|
||||
}
|
||||
|
||||
info(message) {
|
||||
console.info(this.formatLogMessage(message));
|
||||
}
|
||||
|
||||
error(message) {
|
||||
console.error(this.formatLogMessage(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Overridable methods
|
||||
*/
|
||||
|
||||
// The unique key for this Tweet (used for storing the last time this Tweet was posted)
|
||||
getKey() { }
|
||||
|
||||
// The friendly name for this Tweet (used for console log messages)
|
||||
getName() { }
|
||||
|
||||
// The time which the current Tweet is based off of (usually the top of the current hour)
|
||||
getDataTime() {
|
||||
return getTopOfCurrentHour();
|
||||
}
|
||||
|
||||
// The current data item the Tweet is based on (used by getImage and getText)
|
||||
getData() { }
|
||||
|
||||
// Data for test screenshots
|
||||
getTestData() {
|
||||
return this.getData();
|
||||
}
|
||||
|
||||
// The image data to be posted with the Tweet
|
||||
getImage(data) { }
|
||||
|
||||
// The text body of the Tweet
|
||||
getText(data) { }
|
||||
|
||||
// The filename for test screenshots
|
||||
getTestScreenshotFilename() {
|
||||
let key = this.getKey();
|
||||
return `test-screenshot-${key}.png`;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TwitterPostBase;
|
||||
40
src/updater/twitter/client.js
Normal file
40
src/updater/twitter/client.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
require('../bootstrap');
|
||||
const Twitter = require('twitter');
|
||||
|
||||
// Twitter API parameters
|
||||
const consumer_key = process.env.TWITTER_CONSUMER_KEY;
|
||||
const consumer_secret = process.env.TWITTER_CONSUMER_SECRET;
|
||||
const access_token_key = process.env.TWITTER_ACCESS_TOKEN_KEY;
|
||||
const access_token_secret = process.env.TWITTER_ACCESS_TOKEN_SECRET;
|
||||
|
||||
// Twitter API client
|
||||
const client = new Twitter({ consumer_key, consumer_secret, access_token_key, access_token_secret });
|
||||
module.exports.client = client;
|
||||
|
||||
function canTweet() {
|
||||
return consumer_key && consumer_secret && access_token_key && access_token_secret;
|
||||
}
|
||||
module.exports.canTweet = canTweet;
|
||||
|
||||
function postMediaTweet(text, image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Upload the image
|
||||
client.post('media/upload', { media: image }, (error, media, response) => {
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
let status = {
|
||||
status: text,
|
||||
media_ids: media.media_id_string,
|
||||
};
|
||||
|
||||
client.post('statuses/update', status, (error, tweet, response) => {
|
||||
if (error)
|
||||
return reject(error);
|
||||
|
||||
resolve(tweet);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
module.exports.postMediaTweet = postMediaTweet;
|
||||
22
src/updater/twitter/utilities.js
Normal file
22
src/updater/twitter/utilities.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const dataPath = path.resolve('public/data');
|
||||
|
||||
function getTopOfCurrentHour() {
|
||||
let date = new Date;
|
||||
date.setUTCMinutes(0);
|
||||
date.setUTCSeconds(0);
|
||||
return Math.floor(date.getTime() / 1000);
|
||||
}
|
||||
module.exports.getTopOfCurrentHour = getTopOfCurrentHour;
|
||||
|
||||
function readJson(filename) {
|
||||
return JSON.parse(fs.readFileSync(filename));
|
||||
}
|
||||
module.exports.readJson = readJson;
|
||||
|
||||
function readData(filename) {
|
||||
return readJson(`${dataPath}/${filename}`);
|
||||
}
|
||||
module.exports.readData = readData;
|
||||
Loading…
Reference in New Issue
Block a user