Introduces a plugin registry so the display mode is selectable per device via the NixOS module config (defaults to "calendar"). Moves the hardcoded render URL base into config.base_url. Adds tests exercising the plugin system with a synthetic ICS feed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
118 lines
3.3 KiB
TypeScript
118 lines
3.3 KiB
TypeScript
import { SHA256 } from 'bun';
|
|
import { devices } from './devices';
|
|
import { createPlugin } from './plugins';
|
|
import { makeScreenshot } from './render';
|
|
import { render } from './template';
|
|
|
|
let renderMap = new Map();
|
|
|
|
interface DeviceConfiguration {
|
|
plugin?: string;
|
|
settings?: Record<string, any>;
|
|
refresh?: number;
|
|
model: string;
|
|
}
|
|
|
|
interface Configuration {
|
|
base_url: string;
|
|
devices: Record<string, DeviceConfiguration>;
|
|
}
|
|
|
|
const config: Configuration = JSON.parse(
|
|
await Bun.file(Bun.env['CONFIG_FILE'] ?? 'config.json').text()
|
|
);
|
|
|
|
Bun.serve({
|
|
hostname: '0.0.0.0',
|
|
|
|
routes: {
|
|
'/api/setup': async (req) => {
|
|
const id = req.headers.get('ID') ?? 'UNKNOWN';
|
|
const newfriendly = id.replaceAll(':', '');
|
|
|
|
return Response.json({
|
|
status: 200,
|
|
message: 'hi',
|
|
api_key: id,
|
|
friendly_id: newfriendly,
|
|
image_url: 'https://trmnl.com/images/setup/setup-logo.bmp',
|
|
filename: 'empty_state',
|
|
});
|
|
},
|
|
|
|
'/api/display': async (req) => {
|
|
const id = req.headers.get('ID') ?? 'unknown';
|
|
const newfriendly = id.replaceAll(':', '');
|
|
|
|
const device = config.devices[id] ?? {
|
|
plugin: 'calendar',
|
|
settings: {},
|
|
refresh: undefined,
|
|
model: 'inkplate_10',
|
|
};
|
|
|
|
console.log(`Rendering for ${id}..`);
|
|
|
|
let plugin = createPlugin(device.plugin ?? 'calendar', device.settings ?? {});
|
|
const rendered = makeScreenshot(
|
|
[[plugin, 'full']],
|
|
devices.find((a) => a.name === device.model)!
|
|
);
|
|
renderMap.set(id.toLowerCase(), rendered);
|
|
|
|
await Bun.sleep(3500);
|
|
|
|
let nextRefresh = device.refresh ?? 5 * 60;
|
|
if (plugin.nextEvent) {
|
|
let timeUntilNextEvent =
|
|
(plugin.nextEvent.valueOf() - Date.now()) / 1000;
|
|
if (timeUntilNextEvent < 60) timeUntilNextEvent = 60;
|
|
if (device.refresh && timeUntilNextEvent > device.refresh)
|
|
timeUntilNextEvent = device.refresh;
|
|
|
|
if (timeUntilNextEvent < nextRefresh) nextRefresh = timeUntilNextEvent;
|
|
}
|
|
|
|
if (!plugin.hash) {
|
|
console.log(`Had to respond before plugin for ${id} was rendered.`);
|
|
}
|
|
|
|
return Response.json({
|
|
status: 0,
|
|
image_url: `${config.base_url}/api/render/${id.toLowerCase()}/${plugin.hash ?? Math.random()}.png`,
|
|
refresh_rate: nextRefresh.toString(),
|
|
update_firmware: false,
|
|
firmware_url: null,
|
|
reset_firmware: false,
|
|
filename: `${plugin.hash ?? Math.random()}.png`,
|
|
});
|
|
},
|
|
|
|
'/api/display/html': async (req) => {
|
|
const id = req.headers.get('ID') ?? 'unknown';
|
|
const newfriendly = id.replaceAll(':', '');
|
|
|
|
const device = config.devices[id] ?? {
|
|
plugin: 'calendar',
|
|
settings: {},
|
|
refresh: undefined,
|
|
model: 'inkplate_10',
|
|
};
|
|
|
|
let plugin = createPlugin(device.plugin ?? 'calendar', device.settings ?? {});
|
|
const rendered = await render(
|
|
[[plugin, 'full']],
|
|
devices.find((a) => a.name === device.model)!
|
|
);
|
|
return new Response(rendered, {
|
|
headers: { 'Content-Type': 'text/html' },
|
|
});
|
|
},
|
|
|
|
'/api/render/:id/:ignore': async (req) => {
|
|
return new Response(await renderMap.get(req.params.id), {
|
|
headers: { 'Content-Type': 'image/png' },
|
|
});
|
|
},
|
|
},
|
|
});
|