Add automatic Threads link replies and sunrise preview card
This commit is contained in:
+10
-2
@@ -8,6 +8,7 @@ import { kstClock, kstFields } from '../../src/ui/time.ts';
|
||||
import { dailySchedule, publishCarousel, threadsRequest, sunriseCaptureTime } from './publisher.ts';
|
||||
import { loadThreadsConfig } from './config.ts';
|
||||
import { cleanupPublished } from './cleanup.ts';
|
||||
import { replyToPublished } from './reply.ts';
|
||||
import { loadCaptions, renderCaption } from './captions.ts';
|
||||
import { WEATHER } from '../../src/constants/weather.ts';
|
||||
import type { WeatherSample } from '../../src/weather/weather.ts';
|
||||
@@ -69,10 +70,14 @@ async function main() {
|
||||
for (const name of await readdir(stateDir)) {
|
||||
if (/^(?:\d{4}-\d{2}-\d{2}|test-\d{4}-\d{2}-\d{2}-[\da-f-]+)\.json$/.test(name)) {
|
||||
await cleanupPublished(join(stateDir, name), imageDir);
|
||||
// An older failed reply must not prevent today's time-sensitive capture.
|
||||
try { await replyToPublished(join(stateDir, name), accessToken); }
|
||||
catch (error) { console.warn(`Pending reply ${name}: ${error instanceof Error ? error.message : 'failed'}`); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dryRun && await readJson(recordPath)) {
|
||||
await replyToPublished(recordPath, accessToken);
|
||||
console.log(`${schedule.date}: publication already recorded; inspect the record if status is publishing`);
|
||||
return;
|
||||
}
|
||||
@@ -89,9 +94,10 @@ async function main() {
|
||||
throw new Error('Saved capture is not a two-angle capture; archive the old capture record before running again');
|
||||
}
|
||||
if (!dryRun && !savedCapture) await saveJson(capturePath, captured);
|
||||
const { text, condition } = renderCaption(loadCaptions(), { weather: captured.weather ?? null,
|
||||
const { text, condition, replyText } = renderCaption(loadCaptions(), { weather: captured.weather ?? null,
|
||||
capturedAt, sunrise: captured.sunrise, url: siteUrl.origin, test: postNow });
|
||||
console.log(`Caption (${condition}):\n${text}`);
|
||||
if (replyText) console.log(`Reply:\n${replyText}`);
|
||||
if (dryRun) { console.log('Dry run: no Threads API calls or publication records'); return; }
|
||||
// Authentication/network outages must not lose the time-sensitive capture.
|
||||
const token = await accessToken();
|
||||
@@ -102,7 +108,8 @@ async function main() {
|
||||
if (!check.ok || !remoteImage.equals(await readFile(imagePath))) throw new Error('Public image URL does not serve the captured image');
|
||||
}
|
||||
const record = { date: schedule.date, mode: postNow ? 'test' : 'scheduled', sunrise: captured.sunrise,
|
||||
capturedAt, images, weather: captured.weather ?? null, condition, text, status: 'publishing' };
|
||||
capturedAt, images, weather: captured.weather ?? null, condition, text, replyText,
|
||||
replyUrl: siteUrl.origin, status: 'publishing' };
|
||||
const postId = await publishCarousel(token, images.map((image: { imageUrl: string }) => image.imageUrl), text, async containerId => {
|
||||
// Atomic claim survives restarts and blocks concurrent/ambiguous publication retries.
|
||||
await writeFile(recordPath, JSON.stringify({ ...record, containerId }, null, 2), { flag: 'wx', mode: 0o600 });
|
||||
@@ -112,6 +119,7 @@ async function main() {
|
||||
console.log(`Threads published: ${postId}`);
|
||||
await cleanupPublished(recordPath, imageDir);
|
||||
console.log('Deleted both source images after publication');
|
||||
await replyToPublished(recordPath, accessToken);
|
||||
}
|
||||
|
||||
async function captureSunrise(schedule: ReturnType<typeof dailySchedule>, immediate: boolean) {
|
||||
|
||||
Reference in New Issue
Block a user