Add editable weather captions and manage Threads scheduling with PM2
This commit is contained in:
@@ -7,6 +7,13 @@ export const THREADS = {
|
||||
mediaPollMs: 5_000,
|
||||
mediaPollAttempts: 24,
|
||||
tokenRefreshMs: 7 * 86_400_000,
|
||||
captionMaxLength: 500,
|
||||
captionConditions: ['clear', 'partlyCloudy', 'cloudy', 'fog', 'rain', 'snow', 'unknown'],
|
||||
scheduleHourKst: 3,
|
||||
retryAttempts: 5,
|
||||
retryMs: 60_000,
|
||||
jobTimeoutMs: 8 * 60 * 60_000,
|
||||
jobStopGraceMs: 10_000,
|
||||
viewport: { width: 1080, height: 1728 },
|
||||
angles: [
|
||||
{ name: 'sunrise', offset: 0, pitch: 0, alt: '일출을 바라보는 캐릭터의 뒷모습과 관악산 풍경' },
|
||||
|
||||
+3
-1
@@ -128,8 +128,10 @@ export async function start(options: StartOptions = {}) {
|
||||
world.update(state);
|
||||
};
|
||||
const weatherStatus = document.getElementById('weather-status')!;
|
||||
let captureWeather: WeatherStatus = { status: 'loading', weather: null };
|
||||
const applyWeather = ({ status, weather, source }: WeatherStatus) => {
|
||||
if (import.meta.env.DEV && new URLSearchParams(location.search).has('profile')) return;
|
||||
captureWeather = { status, weather, source };
|
||||
world.setWeather(weather);
|
||||
weatherStatus.textContent = status === 'ready' && weather
|
||||
? `${source} · 운량 ${Math.round(weather.cloudCover)}% · ${weather.visibility === null
|
||||
@@ -243,7 +245,7 @@ export async function start(options: StartOptions = {}) {
|
||||
window.addEventListener('pageshow', event => { if (event.persisted && sharedMode) join(); });
|
||||
if (query.get('capture') === 'threads' && !sharedMode) {
|
||||
const { installThreadsCapture } = await import('./ui/threadsCapture.ts');
|
||||
installThreadsCapture(effectiveObserver, world);
|
||||
installThreadsCapture(effectiveObserver, world, () => captureWeather);
|
||||
}
|
||||
options.ready?.();
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import type { ObserverConfig } from '../config/observer.ts';
|
||||
import { calculateSun, sunriseAzimuth } from '../astronomy/sunCalculator.ts';
|
||||
import { THREADS } from '../constants/threads.ts';
|
||||
import type { WeatherSample, WeatherStatus } from '../weather/weather.ts';
|
||||
|
||||
export interface ThreadsCapture {
|
||||
sunrise(): string;
|
||||
capture(): { images: { name: string; image: string }[]; capturedAt: string };
|
||||
weatherStatus(): WeatherStatus['status'];
|
||||
capture(): { images: { name: string; image: string }[]; capturedAt: string; weather: WeatherSample | null };
|
||||
}
|
||||
|
||||
declare global { interface Window { sunriseThreads?: ThreadsCapture } }
|
||||
|
||||
export function installThreadsCapture(observer: ObserverConfig,
|
||||
world: { update: (state: ReturnType<typeof calculateSun>) => void; lookAtSunrise: (azimuth: number) => void;
|
||||
captureAngle: (azimuth: number, pitch: number) => HTMLCanvasElement }) {
|
||||
captureAngle: (azimuth: number, pitch: number) => HTMLCanvasElement }, getWeather: () => WeatherStatus) {
|
||||
window.sunriseThreads = {
|
||||
weatherStatus: () => getWeather().status,
|
||||
sunrise() {
|
||||
const sunrise = calculateSun(new Date(), observer).sunrise;
|
||||
if (!sunrise || !Number.isFinite(+sunrise)) throw new Error('No sunrise');
|
||||
@@ -25,7 +28,8 @@ export function installThreadsCapture(observer: ObserverConfig,
|
||||
world.lookAtSunrise(azimuth);
|
||||
const images = THREADS.angles.map(angle => ({ name: angle.name,
|
||||
image: world.captureAngle((azimuth + angle.offset) % 360, angle.pitch).toDataURL('image/jpeg', 0.95) }));
|
||||
return { images, capturedAt: now.toISOString() };
|
||||
const weather = getWeather();
|
||||
return { images, capturedAt: now.toISOString(), weather: weather.status === 'ready' ? weather.weather : null };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user