Add basic Twitter functionality

This commit is contained in:
Matt Isenhower 2022-09-01 16:51:31 -07:00
parent 4fac5b67cc
commit 7a6677c9f6
12 changed files with 193 additions and 1 deletions

5
.env.example Normal file
View File

@ -0,0 +1,5 @@
# Twitter API parameters
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN_KEY=
TWITTER_ACCESS_TOKEN_SECRET=

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ dist-ssr
coverage
*.local
.env
docker-compose.override.yml
/cypress/videos/

17
app/index.mjs Normal file
View File

@ -0,0 +1,17 @@
import dotenv from 'dotenv';
import { sendTweets } from './twitter/index.mjs';
dotenv.config();
const actions = {
twitter: sendTweets,
}
const command = process.argv[2];
const action = actions[command];
if (action) {
action();
} else {
console.error(`Unrecognized command: ${command}`);
}

5
app/twitter/Media.mjs Normal file
View File

@ -0,0 +1,5 @@
export default class Media
{
file = null;
type = 'image/png';
}

5
app/twitter/Tweet.mjs Normal file
View File

@ -0,0 +1,5 @@
export default class Tweet
{
status = null;
media = [];
}

View File

@ -0,0 +1,30 @@
import { TwitterApi } from "twitter-api-v2";
import Tweet from "./Tweet.mjs";
export default class TwitterClient
{
/** @var {TwitterApi} */
#api;
constructor() {
this.#api = new TwitterApi({
appKey: process.env.TWITTER_CONSUMER_KEY,
appSecret: process.env.TWITTER_CONSUMER_SECRET,
accessToken: process.env.TWITTER_ACCESS_TOKEN_KEY,
accessSecret: process.env.TWITTER_ACCESS_TOKEN_SECRET,
});
}
/**
* @param {Tweet} tweet
*/
async send(tweet) {
// Upload images
let mediaIds = await Promise.all(
tweet.media.map(m => this.#api.v1.uploadMedia(m.file, { mimeType: m.type}))
);
// Send tweet
await this.#api.v1.tweet(tweet.status, { media_ids: mediaIds });
}
}

View File

@ -0,0 +1,28 @@
import TweetGenerator from "./generators/TweetGenerator.mjs";
import TwitterClient from "./TwitterClient.mjs";
export default class TwitterManager
{
/** @var {TweetGenerator[]} */
generators;
/** @var {TwitterClient} */
client;
constructor(generators = []) {
this.generators = generators;
this.client = new TwitterClient;
}
async sendTweets() {
for (let generator of this.generators) {
if (!(await generator.shouldTweet())) {
continue;
}
let tweet = await generator.getTweet();
await this.client.send(tweet);
}
}
}

View File

@ -0,0 +1,34 @@
import TweetGenerator from "./TweetGenerator.mjs";
import { captureScreenshot } from "../../screenshots/captureScreenshot.mjs";
import Media from "../Media.mjs";
const releaseDate = new Date('2022-09-09');
export default class CountdownTweet extends TweetGenerator
{
shouldTweet() {
const now = new Date;
const cutoff = new Date('2022-09-10');
return now < cutoff;
}
_getStatus() {
const now = new Date;
const ms = releaseDate - now;
let days = Math.max(0, Math.ceil(ms / 1000 / 60 / 60 / 24));
switch (days) {
case 0: return 'Splatoon 3 is NOW RELEASED! https://splatoon3.ink #Splatoon3';
case 1: return 'Splatoon 3 releases in 1 DAY! https://splatoon3.ink #Splatoon3';
default: return `Splatoon 3 releases in ${days} days! https://splatoon3.ink #Splatoon3`;
}
}
async _getMedia() {
let media = new Media;
media.file = await captureScreenshot('countdown');
return media;
}
}

View File

@ -0,0 +1,29 @@
import Tweet from '../Tweet.mjs';
export default class TweetGenerator
{
async shouldTweet() {
return true;
}
async getTweet() {
const tweet = new Tweet;
tweet.status = await this._getStatus();
let media = await this._getMedia();
if (media && !Array.isArray(media)) {
media = [media];
}
tweet.media = media;
return tweet;
}
async _getStatus () {
//
}
async _getMedia() {
//
}
}

10
app/twitter/index.mjs Normal file
View File

@ -0,0 +1,10 @@
import CountdownTweet from "./generators/CountdownTweet.mjs";
import TwitterManager from "./TwitterManager.mjs"
export function sendTweets() {
const manager = new TwitterManager([
new CountdownTweet,
]);
return manager.sendTweets();
}

25
package-lock.json generated
View File

@ -8,9 +8,11 @@
"name": "splatoon3.ink",
"version": "0.0.0",
"dependencies": {
"dotenv": "^16.0.2",
"ecstatic": "^4.1.4",
"pinia": "^2.0.13",
"puppeteer": "^17.0.0",
"twitter-api-v2": "^1.12.5",
"vue": "^3.2.33",
"vue-router": "^4.0.14"
},
@ -837,6 +839,14 @@
"node": ">=6.0.0"
}
},
"node_modules/dotenv": {
"version": "16.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz",
"integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA==",
"engines": {
"node": ">=12"
}
},
"node_modules/ecstatic": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.4.tgz",
@ -2905,6 +2915,11 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/twitter-api-v2": {
"version": "1.12.5",
"resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.12.5.tgz",
"integrity": "sha512-kgsEjRfm2kdvAgqXd5druYYxykXEXamae6V/TDyYnws0MClcYBlbtOyGn/HPXEfn2NylbQrjDwkCLaD3qkRgMA=="
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -3762,6 +3777,11 @@
"esutils": "^2.0.2"
}
},
"dotenv": {
"version": "16.0.2",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.2.tgz",
"integrity": "sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA=="
},
"ecstatic": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.4.tgz",
@ -5109,6 +5129,11 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"twitter-api-v2": {
"version": "1.12.5",
"resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.12.5.tgz",
"integrity": "sha512-kgsEjRfm2kdvAgqXd5druYYxykXEXamae6V/TDyYnws0MClcYBlbtOyGn/HPXEfn2NylbQrjDwkCLaD3qkRgMA=="
},
"type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View File

@ -5,12 +5,15 @@
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 5050",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore"
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"twitter": "node app/index.mjs twitter"
},
"dependencies": {
"dotenv": "^16.0.2",
"ecstatic": "^4.1.4",
"pinia": "^2.0.13",
"puppeteer": "^17.0.0",
"twitter-api-v2": "^1.12.5",
"vue": "^3.2.33",
"vue-router": "^4.0.14"
},