Build and Push Docker Image / build-and-push (push) Failing after 21s
Reading plans now track a cross-plan activity log for streaks, surface a combined "today's reading" view on both Home and the Reading Plans page, and let custom plans be edited after creation (including narrowing a book to a specific chapter range). The reader auto-marks a plan's day read after a dwell period, active plans that fall behind their pace get a "catch me up" banner, and a new Settings section wires up daily reading reminders via ntfy.sh, checked once a minute by a small server-side scheduler. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
53 lines
2.0 KiB
JavaScript
53 lines
2.0 KiB
JavaScript
// Daily "don't forget to read" reminders, sent via ntfy.sh (https://ntfy.sh) —
|
|
// a free pub/sub push service: POST a message to a topic URL, anyone
|
|
// subscribed to that topic (via the ntfy app or a browser) gets notified.
|
|
// No API keys or push-subscription plumbing needed.
|
|
import { getUsersWithRemindersEnabled, setLastReminderDate, getReadingProgress } from './db.js';
|
|
|
|
function todayDateString(d = new Date()) {
|
|
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
|
|
}
|
|
|
|
function currentHHMM(d = new Date()) {
|
|
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
}
|
|
|
|
async function sendNtfyReminder(user) {
|
|
const base = (user.ntfyUrl || 'https://ntfy.sh').replace(/\/+$/, '');
|
|
const url = `${base}/${encodeURIComponent(user.ntfyTopic)}`;
|
|
const res = await fetch(url, {
|
|
method: 'POST',
|
|
headers: { Title: 'Reading Plan Reminder', Tags: 'open_book' },
|
|
body: "Don't forget today's reading!",
|
|
});
|
|
if (!res.ok) throw new Error(`ntfy responded ${res.status}`);
|
|
}
|
|
|
|
async function checkReminders() {
|
|
const now = new Date();
|
|
const hhmm = currentHHMM(now);
|
|
const today = todayDateString(now);
|
|
const due = getUsersWithRemindersEnabled().filter(
|
|
(u) => u.reminderTime === hhmm && u.lastReminderDate !== today,
|
|
);
|
|
|
|
for (const user of due) {
|
|
try {
|
|
const progress = getReadingProgress(user.id);
|
|
const hasActivePlan = Array.isArray(progress?.progress) && progress.progress.some((p) => !p.completedAt);
|
|
if (!hasActivePlan) continue; // nothing to nudge them about
|
|
await sendNtfyReminder(user);
|
|
setLastReminderDate(user.id, today);
|
|
} catch (err) {
|
|
console.error(`Reading reminder failed for user ${user.id}:`, err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Checks once a minute whether any user's reminder time has arrived. */
|
|
export function startReminderScheduler() {
|
|
setInterval(() => {
|
|
checkReminders().catch((err) => console.error('Reminder scheduler error:', err));
|
|
}, 60 * 1000);
|
|
}
|