Clean up screenshot capture code

This commit is contained in:
Matt Isenhower 2022-09-08 18:47:29 -07:00
parent ded4177900
commit 5e14688ad9
6 changed files with 116 additions and 55 deletions

View File

@ -10,7 +10,7 @@ export default class HttpServer
return this.#server.address().port;
}
start() {
open() {
return new Promise((resolve, reject) => {
if (this.#server) {
return resolve();
@ -23,7 +23,7 @@ export default class HttpServer
});
}
async stop() {
async close() {
if (this.#server) {
await this.#server.close();
this.#server = null;

View File

@ -0,0 +1,94 @@
import { URL } from 'url';
import puppeteer from 'puppeteer';
import HttpServer from './HttpServer.mjs';
const defaultViewport = {
// Using a 16:9 ratio here by default to match Twitter's image card dimensions
width: 1200,
height: 675,
deviceScaleFactor: 2,
};
export default class ScreenshotHelper
{
/** @type {HttpServer} */
#httpServer = null;
/** @type {puppeteer.Browser} */
#browser = null;
/** @type {puppeteer.Page} */
#page = null;
/** @type {puppeteer.Viewport} */
#viewport = null;
get isOpen() {
return !!this.#browser;
}
/** @type {puppeteer.Page} */
get page() {
return this.#page;
}
async open() {
await this.close();
// Start the HTTP server
this.#httpServer = new HttpServer;
await this.#httpServer.open();
// Launch a new Chrome instance
this.#browser = await puppeteer.launch({
args: [
'--no-sandbox', // Allow running as root inside the Docker container
],
// headless: false, // For testing
});
// Create a new page and set the viewport
this.#page = await this.#browser.newPage();
await this.setViewport();
}
async setViewport(viewport = {}) {
this.#viewport = { ...defaultViewport, viewport };
if (this.#page) {
await this.#page.setViewport(this.#viewport);
}
}
async capture(path, options = {}) {
if (!this.isOpen) {
await this.open();
}
await this.setViewport(options.viewport);
// Navigate to the URL
let url = new URL(`http://localhost:${this.#httpServer.port}/screenshots/`);
url.hash = path;
await this.#page.goto(url, {
waitUntil: 'networkidle0', // Wait until the network is idle
});
// Take the screenshot
return await this.#page.screenshot();
}
async close() {
if (this.#httpServer) {
await this.#httpServer.close();
}
this.#httpServer = null;
if (this.#page) {
await this.#page.close();
}
this.#page = null;
if (this.#browser) {
await this.#browser.close();
}
this.#browser = null;
}
}

View File

@ -1,44 +0,0 @@
import { URL } from 'url';
import puppeteer from 'puppeteer';
import HttpServer from './HttpServer.mjs';
const defaultViewport = {
// Using a 16:9 ratio here by default to match Twitter's image card dimensions
width: 1200,
height: 675,
deviceScaleFactor: 2,
};
export async function captureScreenshot(path, options = {}) {
const httpServer = new HttpServer;
await httpServer.start();
// Launch a new Chrome instance
const browser = await puppeteer.launch({
args: [
'--no-sandbox', // Allow running as root inside the Docker container
],
// headless: false, // For testing
});
// Create a new page and set the viewport
const page = await browser.newPage();
const viewport = { ...defaultViewport, ...options.viewport };
await page.setViewport(viewport);
// Navigate to the URL
let url = new URL(`http://localhost:${httpServer.port}/screenshots/`);
url.hash = path;
await page.goto(url, {
waitUntil: 'networkidle0', // Wait until the network is idle
});
// Take the screenshot
let result = await page.screenshot();
// Close the browser and HTTP server
await browser.close();
await httpServer.stop();
return result;
}

View File

@ -1,17 +1,22 @@
import ScreenshotHelper from "../screenshots/ScreenshotHelper.mjs";
import TweetGenerator from "./generators/TweetGenerator.mjs";
import TwitterClient from "./TwitterClient.mjs";
export default class TwitterManager
{
/** @var {TweetGenerator[]} */
/** @type {TweetGenerator[]} */
generators;
/** @var {TwitterClient} */
/** @type {TwitterClient} */
client;
/** @type {ScreenshotHelper} */
screenshotHelper;
constructor(generators = []) {
this.generators = generators;
this.client = new TwitterClient;
this.screenshotHelper = new ScreenshotHelper;
}
async sendTweets() {
@ -20,9 +25,11 @@ export default class TwitterManager
continue;
}
let tweet = await generator.getTweet();
let tweet = await generator.getTweet(this.screenshotHelper);
await this.client.send(tweet);
}
await this.screenshotHelper.close();
}
}

View File

@ -1,6 +1,6 @@
import TweetGenerator from "./TweetGenerator.mjs";
import { captureScreenshot } from "../../screenshots/captureScreenshot.mjs";
import Media from "../Media.mjs";
import ScreenshotHelper from "../../screenshots/ScreenshotHelper.mjs";
const releaseDate = new Date('2022-09-09');
@ -25,9 +25,10 @@ export default class CountdownTweet extends TweetGenerator
}
}
async _getMedia() {
/** @param {ScreenshotHelper} screenshotHelper */
async _getMedia(screenshotHelper) {
let media = new Media;
media.file = await captureScreenshot('countdown');
media.file = await screenshotHelper.capture('countdown');
return media;
}

View File

@ -1,3 +1,4 @@
import ScreenshotHelper from '../../screenshots/ScreenshotHelper.mjs';
import Tweet from '../Tweet.mjs';
export default class TweetGenerator
@ -6,11 +7,12 @@ export default class TweetGenerator
return true;
}
async getTweet() {
/** @param {ScreenshotHelper} screenshotHelper */
async getTweet(screenshotHelper) {
const tweet = new Tweet;
tweet.status = await this._getStatus();
let media = await this._getMedia();
let media = await this._getMedia(screenshotHelper);
if (media && !Array.isArray(media)) {
media = [media];
}
@ -23,7 +25,8 @@ export default class TweetGenerator
//
}
async _getMedia() {
/** @param {ScreenshotHelper} screenshotHelper */
async _getMedia(screenshotHelper) {
//
}
}