Add streaks, today view, custom plan editing/chapter ranges, and ntfy reminders
Build and Push Docker Image / build-and-push (push) Failing after 21s
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>
This commit is contained in:
1 parent
600b3d3ab9
commit
1dd6aef6a5
10 files changed
+711
-141
No files matched your search
@@ -0,0 +1,52 @@
|
||||
// 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);
|
||||
}
|
||||
Reference in new issue
Block a user