TreeMap
A free, open-source disk-space visualizer. Squarified treemap, duplicate hunting, disk-full forecasting, and cleanup that only ever moves files to the Trash.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证FamCal — multi-platform (Telegram + WhatsApp) AI calendar assistant: voice/photo → events, AI daily summaries, TTS. Next.js · Prisma · multi-LLM
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗An AI family-calendar assistant that lives in your chat app — morning and evening briefings, voice-note and photo event capture, and shared couple calendars, across Telegram and WhatsApp.
🔗 Live demo: https://famcal.bot
FamCal is a multi-platform, multilingual calendar assistant for individuals and families. Use the built-in FamCal calendar, optionally connect Google Calendar too, and the bot writes personalized morning and evening briefings, week-ahead previews, weather, and reminders — and turns voice notes and photos of invitations into calendar events. It runs as one persistent Node process on Railway, and every AI call goes through a single provider layer on Google Gemini.
app/[locale]/(site)): static pages, no Telegram SDK, one cached style sheet built from the app's colour tokens (components/app-ui/tokens.ts).app/[locale]/(app), their own root layout with the Telegram SDK) for settings, calendar selection, pairing, and an admin panel with moderation, a persistent blocklist, and analytics.One AI interface. src/services/ai-provider.ts puts every text call behind a single generateAICompletion call. Every call runs on one model, Gemini 3.8 Flash, fixed in a typed model registry (src/config/ai-models.ts), so changing the model is a one-line code change. The layer adds its own retry/backoff (SDK retries are disabled in favor of explicit control), 25-second client timeouts, token accounting (including cache and thinking tokens), and admin alerting when a response hits its output-token ceiling.
Streaming UX. User-invoked summaries use a sibling streaming path (src/services/streaming/text-stream.ts) that emits text deltas as the model writes, which the Telegram adapter renders as an animated, progressively-updating message draft with live HTML tag-balancing so partial markup never breaks Telegram's parser. Because a partial stream can't be safely retried mid-flight, the streaming path deliberately drops the retry wrapper and callers fall back to the buffered path on failure.
Adapter/factory messaging. src/services/messaging/ defines an IMessagingService interface implemented by a TelegramAdapter and a WhatsAppAdapter, created through a small factory with per-platform singletons. Platform detection routes inbound webhooks and enables cross-platform notifications, so the summary, reminder, and event-capture logic is written once against the interface rather than per channel.
A calendar model that stands on its own. The Prisma schema (~370 lines) models a native recurring calendar with RFC-5545 RRULE strings, per-instance overrides and cancellations, and series-level exdates; src/services/native-calendar/recurrence.ts expands series into virtual instances for any date range using rrule. The same schema carries couple pairing with invites and status history, subscriptions/trials/usage counters, and a blocklist that persists across account deletion for moderation.
Delivery correctness. Scheduled jobs (daily and tomorrow summaries, reminders) run in-process on node-cron, and Upstash Redis dedup keys and locks keep a retried or duplicated run from double-sending, with Upstash rate limiting on hot paths. Auth spans magic-link/session flows for the dashboard, Telegram login verification, webhook secret validation, and a CRON_SECRET guarding internal endpoints. Every call to Google goes through one retry policy (src/services/google-retry.ts): a rate limit, a timeout or a 5xx is retried with jittered backoff and Google's Retry-After, and a create is never repeated on a failure that may have gone through. Errors flow to Sentry and to admin alerts. Voice replies and the two-host voice show are synthesized in-process with Gemini TTS and encoded from PCM to OGG/Opus.
rrule (RFC-5545 recurrence).DATABASE_URL + DIRECT_URL)Copy .env.example (production) or .env.local.example (local dev) and fill in your own values. Names only — never commit real secrets.
| Variable | Purpose |
|---|---|
TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET | Telegram bot auth and webhook verification |
TELEGRAM_BOT_USERNAME, WHATSAPP_PHONE_NUMBER | The bot's username and the WhatsApp number (digits only) behind the t.me and wa.me links, such as the Google connect finish buttons; unset, they fall back to the public bot and number in src/lib/gate-links.ts |
WHATSAPP_ACCESS_TOKEN, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_WEBHOOK_VERIFY_TOKEN | WhatsApp Business Cloud API (optional) |
WHATSAPP_APP_SECRET | Required. The Meta app secret, which verifies WhatsApp webhook signatures (X-Hub-Signature-256); unset, every WhatsApp webhook is refused (500) and production startup logs an error |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Google Calendar OAuth |
GEMINI_API_KEY | Gemini API key for text and voice |
AI_MAX_TOKENS, AI_MAX_RETRIES | Optional output-token and retry overrides |
DATABASE_URL, DIRECT_URL | Postgres connection (pooled + direct for migrations) |
UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN | Redis for locks, caching, rate limiting |
CRON_SECRET | Shared secret guarding internal cron/usage endpoints |
SESSION_SECRET | Required, no fallback: its own random value (openssl rand -hex 32), never CRON_SECRET's. The WhatsApp session cookie and the share-story image links are signed with keys derived from it; unset, both are refused and production startup logs an error. Changing it signs WhatsApp web users out once |
ENCRYPTION_KEY | Encrypts the stored Google tokens: 64 hex characters (openssl rand -hex 32). See Rotating ENCRYPTION_KEY |
ENCRYPTION_KEY_PREVIOUS |
The models are not configurable: text and TTS are fixed in src/config/ai-models.ts and src/config/tts-models.ts, and the voices per language in src/services/voice-generator.ts.
ENCRYPTION_KEYENCRYPTION_KEY encrypts every stored Google token. Never replace ENCRYPTION_KEY without setting ENCRYPTION_KEY_PREVIOUS to the old value in the same deploy. Without it, every Google-connected user counts as not connected until the old key is back.
Before rotating, the previous deploy's [key-sweep] baseline: line must show unreadable=0 for users and links. A token that neither key opens blocks SAFE for good; find out why first (the ids are in the [key-sweep] N stored tokens decrypt with neither key (ids only) line logged right after the baseline, and in the admin alert).
Generate the new key with openssl rand -hex 32.
In one deploy, set ENCRYPTION_KEY_PREVIOUS to the current key and ENCRYPTION_KEY to the new one. The startup log prints both fingerprints ([startup] ENCRYPTION_KEY fp …; ENCRYPTION_KEY_PREVIOUS fp …). The PREVIOUS fingerprint must equal the ENCRYPTION_KEY fingerprint the deploy before printed.
The key sweep (src/services/key-rotation.ts) runs every 10 minutes and is idle for the first 10 minutes of uptime. It logs a [key-sweep] baseline: line first. Then it re-encrypts every stored token under the new key with compare-and-set writes and rebuilds the reminder cache.
Once nothing needs the old key, after at least 30 minutes of uptime, it logs this line once per process and tells the admins on Telegram once:
[key-sweep] SAFE: every stored token is under ENCRYPTION_KEY (fp XXXXXXXX); nothing needs ENCRYPTION_KEY_PREVIOUS. You can remove it at the next deploy.
Delete ENCRYPTION_KEY_PREVIOUS at a later deploy. If it is removed before SAFE, the tokens not yet converted can't be read; put it back and they read again.
To roll back, swap the two values: the sweep converts back. The sweep stops reading the database until the next deploy once SAFE is logged, or, with no ENCRYPTION_KEY_PREVIOUS, once nothing is left to encrypt. That way Neon can still scale to zero.
# install dependencies
npm install
# local development: runs the Next.js app and the bot in polling mode together
npm run dev
# (optional) the Next.js dashboard only
npm run dev:next
# type-check
npm run type-check
# CI (GitHub Actions) runs type-check, lint, the smoke tests and `next build` on every push;
# run the smokes locally with fake env:
bash scripts/ci-smokes.sh
# production build (generates Prisma client, applies migrations, builds Next.js)
npm run build
npm run start
Helper scripts handle one-time setup: npm run get-google-token (obtain a Google refresh token) and npm run setup-webhook (register the Telegram webhook).
Deployed on Railway as one persistent Next.js (Node) process, with zero-downtime deploys. npm run build runs prisma generate && prisma migrate deploy && next build, so database migrations are applied on each deploy. Scheduled work (daily/tomorrow summaries, reminders, setup reminders, the Google copy worker, a nightly health check, the encryption-key sweep) runs in-process on node-cron, started from instrumentation.ts into src/cron/scheduler.ts (the job logic is in src/cron/handlers.ts); the health check and the key sweep run only on the schedule; every other job also has an API endpoint for manual runs, guarded by CRON_SECRET sent as Authorization: Bearer <CRON_SECRET> (a ?secret= query is refused). Cron times are UTC (node-cron follows the server process's zone, which the startup log names) except the setup-reminder nudge, which keeps 10:00 Israel time summer and winter (an hourly UTC job that acts when Israel's hour is 10; node-cron's own timezone option skips the day the clocks spring forward). Local wall times become instants in one function, src/utils/wall-time.ts: a time the clocks skip moves forward by the jump, a repeated one is its first occurrence, whatever zone the server runs in.
MIT — shared publicly as a portfolio project.
Optional and decrypt-only: the old ENCRYPTION_KEY while a rotation runs; delete it once the key sweep logs SAFE |
SENTRY_DSN, NEXT_PUBLIC_SENTRY_DSN, SENTRY_ORG, SENTRY_PROJECT | Error tracking |
NODE_ENV | Runtime environment |