mirror of
https://github.com/misenhower/splatoon3.ink.git
synced 2026-03-21 17:54:13 -05:00
Replace # private class members with _ prefix convention for consistency
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
111118cbb0
commit
94aa3d7ece
|
|
@ -4,29 +4,29 @@ import sirv from 'sirv';
|
|||
export default class HttpServer
|
||||
{
|
||||
/** @member {http.Server} */
|
||||
#server = null;
|
||||
_server = null;
|
||||
|
||||
get port() {
|
||||
return this.#server.address().port;
|
||||
return this._server.address().port;
|
||||
}
|
||||
|
||||
open() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.#server) {
|
||||
if (this._server) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
const handler = sirv('./dist');
|
||||
this.#server = http.createServer(handler);
|
||||
this.#server.on('listening', () => resolve());
|
||||
this.#server.listen();
|
||||
this._server = http.createServer(handler);
|
||||
this._server.on('listening', () => resolve());
|
||||
this._server.listen();
|
||||
});
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.#server) {
|
||||
await this.#server.close();
|
||||
this.#server = null;
|
||||
if (this._server) {
|
||||
await this._server.close();
|
||||
this._server = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,43 +12,43 @@ const defaultViewport = {
|
|||
export default class ScreenshotHelper
|
||||
{
|
||||
/** @type {HttpServer} */
|
||||
#httpServer = null;
|
||||
_httpServer = null;
|
||||
/** @type {puppeteer.Browser} */
|
||||
#browser = null;
|
||||
_browser = null;
|
||||
/** @type {puppeteer.Page} */
|
||||
#page = null;
|
||||
_page = null;
|
||||
|
||||
defaultParams = null;
|
||||
|
||||
get isOpen() {
|
||||
return !!this.#browser;
|
||||
return !!this._browser;
|
||||
}
|
||||
|
||||
/** @type {puppeteer.Page} */
|
||||
get page() {
|
||||
return this.#page;
|
||||
return this._page;
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.close();
|
||||
|
||||
// Start the HTTP server
|
||||
this.#httpServer = new HttpServer;
|
||||
await this.#httpServer.open();
|
||||
this._httpServer = new HttpServer;
|
||||
await this._httpServer.open();
|
||||
|
||||
// Connect to Browserless
|
||||
this.#browser = await puppeteer.connect({
|
||||
this._browser = await puppeteer.connect({
|
||||
browserWSEndpoint: process.env.BROWSERLESS_ENDPOINT,
|
||||
});
|
||||
|
||||
// Create a new page and set the viewport
|
||||
this.#page = await this.#browser.newPage();
|
||||
this._page = await this._browser.newPage();
|
||||
await this.applyViewport();
|
||||
}
|
||||
|
||||
async applyViewport(viewport = {}) {
|
||||
if (this.#page) {
|
||||
await this.#page.setViewport({
|
||||
if (this._page) {
|
||||
await this._page.setViewport({
|
||||
...defaultViewport,
|
||||
...viewport,
|
||||
});
|
||||
|
|
@ -64,7 +64,7 @@ export default class ScreenshotHelper
|
|||
|
||||
// Navigate to the URL
|
||||
let host = process.env.SCREENSHOT_HOST || 'localhost';
|
||||
let url = new URL(`http://${host}:${this.#httpServer.port}/screenshots/`);
|
||||
let url = new URL(`http://${host}:${this._httpServer.port}/screenshots/`);
|
||||
url.hash = path;
|
||||
|
||||
let params = {
|
||||
|
|
@ -80,31 +80,31 @@ export default class ScreenshotHelper
|
|||
.join('&');
|
||||
}
|
||||
|
||||
await this.#page.goto(url, {
|
||||
await this._page.goto(url, {
|
||||
waitUntil: 'networkidle0', // Wait until the network is idle
|
||||
});
|
||||
|
||||
// Wait an additional 1000ms
|
||||
await this.#page.waitForNetworkIdle({ idleTime: 1000 });
|
||||
await this._page.waitForNetworkIdle({ idleTime: 1000 });
|
||||
|
||||
// Take the screenshot
|
||||
return await this.#page.screenshot();
|
||||
return await this._page.screenshot();
|
||||
}
|
||||
|
||||
async close() {
|
||||
if (this.#httpServer) {
|
||||
await this.#httpServer.close();
|
||||
if (this._httpServer) {
|
||||
await this._httpServer.close();
|
||||
}
|
||||
this.#httpServer = null;
|
||||
this._httpServer = null;
|
||||
|
||||
if (this.#page) {
|
||||
await this.#page.close();
|
||||
if (this._page) {
|
||||
await this._page.close();
|
||||
}
|
||||
this.#page = null;
|
||||
this._page = null;
|
||||
|
||||
if (this.#browser) {
|
||||
await this.#browser.close();
|
||||
if (this._browser) {
|
||||
await this._browser.close();
|
||||
}
|
||||
this.#browser = null;
|
||||
this._browser = null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,16 @@ export default class StatusGeneratorManager
|
|||
}
|
||||
|
||||
async sendStatuses(force = false) {
|
||||
let availableClients = await this.#getAvailableClients();
|
||||
let availableClients = await this._getAvailableClients();
|
||||
|
||||
// Create screenshots in parallel (via Browserless)
|
||||
let statusPromises = this.#getStatuses(availableClients, force);
|
||||
let statusPromises = this._getStatuses(availableClients, force);
|
||||
|
||||
// Process each client in parallel (while maintaining post order)
|
||||
await this.#sendStatusesToClients(statusPromises, availableClients);
|
||||
await this._sendStatusesToClients(statusPromises, availableClients);
|
||||
}
|
||||
|
||||
async #getAvailableClients() {
|
||||
async _getAvailableClients() {
|
||||
let clients = [];
|
||||
|
||||
for (let client of this.clients) {
|
||||
|
|
@ -47,11 +47,11 @@ export default class StatusGeneratorManager
|
|||
return clients;
|
||||
}
|
||||
|
||||
#getStatuses(availableClients, force) {
|
||||
return this.generators.map(generator => this.#getStatus(availableClients, generator, force));
|
||||
_getStatuses(availableClients, force) {
|
||||
return this.generators.map(generator => this._getStatus(availableClients, generator, force));
|
||||
}
|
||||
|
||||
async #getStatus(availableClients, generator, force) {
|
||||
async _getStatus(availableClients, generator, force) {
|
||||
let screenshotHelper = new ScreenshotHelper;
|
||||
try {
|
||||
let clients = [];
|
||||
|
|
@ -82,23 +82,23 @@ export default class StatusGeneratorManager
|
|||
return null;
|
||||
}
|
||||
|
||||
#sendStatusesToClients(statusPromises, availableClients) {
|
||||
return Promise.allSettled(availableClients.map(client => this.#sendStatusesToClient(statusPromises, client)));
|
||||
_sendStatusesToClients(statusPromises, availableClients) {
|
||||
return Promise.allSettled(availableClients.map(client => this._sendStatusesToClient(statusPromises, client)));
|
||||
}
|
||||
|
||||
async #sendStatusesToClient(statusPromises, client) {
|
||||
async _sendStatusesToClient(statusPromises, client) {
|
||||
for (let promise of statusPromises) {
|
||||
let statusDetails = await promise;
|
||||
|
||||
if (statusDetails && statusDetails.clients.includes(client)) {
|
||||
let { generator, status } = statusDetails;
|
||||
|
||||
await this.#sendToClient(generator, status, client);
|
||||
await this._sendToClient(generator, status, client);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async #sendToClient(generator, status, client) {
|
||||
async _sendToClient(generator, status, client) {
|
||||
this.console(generator, client).info('Posting...');
|
||||
try {
|
||||
await client.send(status, generator);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class BlueskyClient extends Client
|
|||
key = 'bluesky';
|
||||
name = 'Bluesky';
|
||||
|
||||
#agent;
|
||||
_agent;
|
||||
|
||||
async canSend() {
|
||||
return process.env.BLUESKY_SERVICE
|
||||
|
|
@ -18,12 +18,12 @@ export default class BlueskyClient extends Client
|
|||
}
|
||||
|
||||
async login() {
|
||||
if (!this.#agent) {
|
||||
this.#agent = new BskyAgent({
|
||||
if (!this._agent) {
|
||||
this._agent = new BskyAgent({
|
||||
service: process.env.BLUESKY_SERVICE,
|
||||
});
|
||||
|
||||
await this.#agent.login({
|
||||
await this._agent.login({
|
||||
identifier: process.env.BLUESKY_IDENTIFIER,
|
||||
password: process.env.BLUESKY_PASSWORD,
|
||||
});
|
||||
|
|
@ -34,9 +34,9 @@ export default class BlueskyClient extends Client
|
|||
await this.login();
|
||||
|
||||
let jpeg = await sharp(avatarBuffer).jpeg().toBuffer();
|
||||
let uploadResponse = await this.#agent.uploadBlob(jpeg, { encoding: 'image/jpeg' });
|
||||
let uploadResponse = await this._agent.uploadBlob(jpeg, { encoding: 'image/jpeg' });
|
||||
|
||||
await this.#agent.upsertProfile((existing) => {
|
||||
await this._agent.upsertProfile((existing) => {
|
||||
return {
|
||||
...existing,
|
||||
avatar: uploadResponse.data.blob,
|
||||
|
|
@ -62,7 +62,7 @@ export default class BlueskyClient extends Client
|
|||
let metadata = await jpeg.metadata();
|
||||
let buffer = await jpeg.toBuffer();
|
||||
|
||||
let response = await this.#agent.uploadBlob(buffer, { encoding: 'image/jpeg' });
|
||||
let response = await this._agent.uploadBlob(buffer, { encoding: 'image/jpeg' });
|
||||
|
||||
return {
|
||||
image: response.data.blob,
|
||||
|
|
@ -77,9 +77,9 @@ export default class BlueskyClient extends Client
|
|||
text: status.status,
|
||||
});
|
||||
|
||||
await rt.detectFacets(this.#agent);
|
||||
await rt.detectFacets(this._agent);
|
||||
|
||||
await this.#agent.post({
|
||||
await this._agent.post({
|
||||
text: rt.text,
|
||||
facets: rt.facets,
|
||||
embed: {
|
||||
|
|
|
|||
|
|
@ -6,26 +6,26 @@ export default class MastodonClient extends Client
|
|||
key = 'mastodon';
|
||||
name = 'Mastodon';
|
||||
|
||||
#url;
|
||||
#accessToken;
|
||||
#visibility;
|
||||
_url;
|
||||
_accessToken;
|
||||
_visibility;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#url = process.env.MASTODON_URL;
|
||||
this.#accessToken = process.env.MASTODON_ACCESS_TOKEN;
|
||||
this.#visibility = process.env.MASTODON_VISIBILITY || 'public';
|
||||
this._url = process.env.MASTODON_URL;
|
||||
this._accessToken = process.env.MASTODON_ACCESS_TOKEN;
|
||||
this._visibility = process.env.MASTODON_VISIBILITY || 'public';
|
||||
}
|
||||
|
||||
async canSend() {
|
||||
return this.#url && this.#accessToken;
|
||||
return this._url && this._accessToken;
|
||||
}
|
||||
|
||||
async updateProfile(avatarBuffer, displayName) {
|
||||
const masto = await createRestAPIClient({
|
||||
url: this.#url,
|
||||
accessToken: this.#accessToken,
|
||||
url: this._url,
|
||||
accessToken: this._accessToken,
|
||||
disableVersionCheck: true,
|
||||
disableExperimentalWarning: true,
|
||||
});
|
||||
|
|
@ -45,8 +45,8 @@ export default class MastodonClient extends Client
|
|||
try {
|
||||
// Mastodon API
|
||||
const masto = await createRestAPIClient({
|
||||
url: this.#url,
|
||||
accessToken: this.#accessToken,
|
||||
url: this._url,
|
||||
accessToken: this._accessToken,
|
||||
disableVersionCheck: true,
|
||||
disableExperimentalWarning: true,
|
||||
});
|
||||
|
|
@ -71,7 +71,7 @@ export default class MastodonClient extends Client
|
|||
spoilerText: status.contentWrapper,
|
||||
sensitive: !!status.contentWrapper, // Without the sensitive property the image is still visible
|
||||
mediaIds,
|
||||
visibility: this.#visibility,
|
||||
visibility: this._visibility,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[${this.name}] Failed to post ${generator.key}:`, error.message);
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ export default class ThreadsClient extends Client {
|
|||
key = 'threads';
|
||||
name = 'Threads';
|
||||
|
||||
#baseUrl = 'https://graph.threads.net/v1.0';
|
||||
#tokenCache = new ValueCache('threads.token');
|
||||
#accessToken;
|
||||
_baseUrl = 'https://graph.threads.net/v1.0';
|
||||
_tokenCache = new ValueCache('threads.token');
|
||||
_accessToken;
|
||||
|
||||
get console() {
|
||||
return this._console ??= prefixedConsole('Social', this.name);
|
||||
|
|
@ -23,32 +23,32 @@ export default class ThreadsClient extends Client {
|
|||
&& process.env.AWS_S3_ENDPOINT;
|
||||
}
|
||||
|
||||
async #getAccessToken() {
|
||||
if (this.#accessToken) {
|
||||
return this.#accessToken;
|
||||
async _getAccessToken() {
|
||||
if (this._accessToken) {
|
||||
return this._accessToken;
|
||||
}
|
||||
|
||||
// Try to use a previously refreshed token from cache
|
||||
let cached = await this.#tokenCache.getData();
|
||||
let cached = await this._tokenCache.getData();
|
||||
if (cached) {
|
||||
this.#accessToken = cached;
|
||||
return this.#accessToken;
|
||||
this._accessToken = cached;
|
||||
return this._accessToken;
|
||||
}
|
||||
|
||||
// Fall back to the .env token
|
||||
this.#accessToken = process.env.THREADS_ACCESS_TOKEN;
|
||||
return this.#accessToken;
|
||||
this._accessToken = process.env.THREADS_ACCESS_TOKEN;
|
||||
return this._accessToken;
|
||||
}
|
||||
|
||||
async #refreshToken() {
|
||||
let currentToken = await this.#getAccessToken();
|
||||
async _refreshToken() {
|
||||
let currentToken = await this._getAccessToken();
|
||||
|
||||
let params = new URLSearchParams({
|
||||
grant_type: 'th_refresh_token',
|
||||
access_token: currentToken,
|
||||
});
|
||||
|
||||
let response = await fetch(`${this.#baseUrl}/refresh_access_token?${params}`);
|
||||
let response = await fetch(`${this._baseUrl}/refresh_access_token?${params}`);
|
||||
let data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
|
|
@ -56,11 +56,11 @@ export default class ThreadsClient extends Client {
|
|||
return;
|
||||
}
|
||||
|
||||
this.#accessToken = data.access_token;
|
||||
this._accessToken = data.access_token;
|
||||
|
||||
// Cache the token with its expiry
|
||||
let expires = new Date(Date.now() + data.expires_in * 1000);
|
||||
await this.#tokenCache.setData(data.access_token, expires);
|
||||
await this._tokenCache.setData(data.access_token, expires);
|
||||
|
||||
this.console.log(`Token refreshed, expires ${expires.toISOString()}`);
|
||||
}
|
||||
|
|
@ -73,30 +73,30 @@ export default class ThreadsClient extends Client {
|
|||
|
||||
try {
|
||||
// Refresh the token if it hasn't been refreshed in the last 24 hours
|
||||
let lastRefresh = await this.#tokenCache.getCachedAt();
|
||||
let lastRefresh = await this._tokenCache.getCachedAt();
|
||||
let oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
if (!lastRefresh || lastRefresh < oneDayAgo) {
|
||||
await this.#refreshToken();
|
||||
await this._refreshToken();
|
||||
}
|
||||
|
||||
let accessToken = await this.#getAccessToken();
|
||||
let accessToken = await this._getAccessToken();
|
||||
let jpeg = await sharp(status.media[0].file).jpeg().toBuffer();
|
||||
|
||||
// Upload image to S3 so it's publicly accessible
|
||||
let { imageUrl, s3Key } = await this.#uploadImage(jpeg, generator.key);
|
||||
let { imageUrl, s3Key } = await this._uploadImage(jpeg, generator.key);
|
||||
|
||||
try {
|
||||
// Create a media container
|
||||
let containerId = await this.#createContainer(status.status, imageUrl, accessToken);
|
||||
let containerId = await this._createContainer(status.status, imageUrl, accessToken);
|
||||
|
||||
// Wait for the container to finish processing
|
||||
await this.#waitForContainer(containerId, accessToken);
|
||||
await this._waitForContainer(containerId, accessToken);
|
||||
|
||||
// Publish the container
|
||||
await this.#publish(containerId, accessToken);
|
||||
await this._publish(containerId, accessToken);
|
||||
} finally {
|
||||
// Clean up the temporary image from S3
|
||||
await this.#deleteImage(s3Key);
|
||||
await this._deleteImage(s3Key);
|
||||
}
|
||||
} catch (error) {
|
||||
this.console.error(`Failed to post ${generator.key}:`, error.message);
|
||||
|
|
@ -104,7 +104,7 @@ export default class ThreadsClient extends Client {
|
|||
}
|
||||
}
|
||||
|
||||
async #uploadImage(buffer, key) {
|
||||
async _uploadImage(buffer, key) {
|
||||
let s3 = new S3Client({
|
||||
endpoint: process.env.AWS_S3_ENDPOINT,
|
||||
region: process.env.AWS_REGION,
|
||||
|
|
@ -130,7 +130,7 @@ export default class ThreadsClient extends Client {
|
|||
return { imageUrl, s3Key };
|
||||
}
|
||||
|
||||
async #deleteImage(s3Key) {
|
||||
async _deleteImage(s3Key) {
|
||||
try {
|
||||
let s3 = new S3Client({
|
||||
endpoint: process.env.AWS_S3_ENDPOINT,
|
||||
|
|
@ -150,7 +150,7 @@ export default class ThreadsClient extends Client {
|
|||
}
|
||||
}
|
||||
|
||||
async #createContainer(text, imageUrl, accessToken) {
|
||||
async _createContainer(text, imageUrl, accessToken) {
|
||||
let userId = process.env.THREADS_USER_ID;
|
||||
let params = new URLSearchParams({
|
||||
media_type: 'IMAGE',
|
||||
|
|
@ -159,7 +159,7 @@ export default class ThreadsClient extends Client {
|
|||
access_token: accessToken,
|
||||
});
|
||||
|
||||
let response = await fetch(`${this.#baseUrl}/${userId}/threads`, {
|
||||
let response = await fetch(`${this._baseUrl}/${userId}/threads`, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
|
@ -177,7 +177,7 @@ export default class ThreadsClient extends Client {
|
|||
return data.id;
|
||||
}
|
||||
|
||||
async #waitForContainer(containerId, accessToken) {
|
||||
async _waitForContainer(containerId, accessToken) {
|
||||
// Poll container status until it's ready (or timeout after 60s)
|
||||
let maxAttempts = 12;
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ export default class ThreadsClient extends Client {
|
|||
access_token: accessToken,
|
||||
});
|
||||
|
||||
let response = await fetch(`${this.#baseUrl}/${containerId}?${params}`);
|
||||
let response = await fetch(`${this._baseUrl}/${containerId}?${params}`);
|
||||
let data = await response.json();
|
||||
|
||||
if (data.status === 'FINISHED') {
|
||||
|
|
@ -205,14 +205,14 @@ export default class ThreadsClient extends Client {
|
|||
throw new Error('Container processing timed out');
|
||||
}
|
||||
|
||||
async #publish(containerId, accessToken) {
|
||||
async _publish(containerId, accessToken) {
|
||||
let userId = process.env.THREADS_USER_ID;
|
||||
let params = new URLSearchParams({
|
||||
creation_id: containerId,
|
||||
access_token: accessToken,
|
||||
});
|
||||
|
||||
let response = await fetch(`${this.#baseUrl}/${userId}/threads_publish`, {
|
||||
let response = await fetch(`${this._baseUrl}/${userId}/threads_publish`, {
|
||||
method: 'POST',
|
||||
body: params,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export default class TwitterClient extends Client
|
|||
name = 'Twitter';
|
||||
|
||||
/** @member {TwitterApi} */
|
||||
#api;
|
||||
_api;
|
||||
|
||||
async canSend() {
|
||||
return process.env.TWITTER_CONSUMER_KEY
|
||||
|
|
@ -17,8 +17,8 @@ export default class TwitterClient extends Client
|
|||
}
|
||||
|
||||
api() {
|
||||
if (!this.#api) {
|
||||
this.#api = new TwitterApi({
|
||||
if (!this._api) {
|
||||
this._api = new TwitterApi({
|
||||
appKey: process.env.TWITTER_CONSUMER_KEY,
|
||||
appSecret: process.env.TWITTER_CONSUMER_SECRET,
|
||||
accessToken: process.env.TWITTER_ACCESS_TOKEN_KEY,
|
||||
|
|
@ -26,7 +26,7 @@ export default class TwitterClient extends Client
|
|||
});
|
||||
}
|
||||
|
||||
return this.#api;
|
||||
return this._api;
|
||||
}
|
||||
|
||||
async send(status, generator) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user