Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Find a Catholic Mass in Singapore you can actually make: from where you are, by bus & MRT, car or on foot, and when to leave.README 전문 보기 ↗Find a Catholic Mass in Singapore you can actually make: from where you are, by bus & MRT, car or on foot, and when to leave.
massgowhere, auto-deploys on every push to main). The old mass.anselmlong.com stays attached as an alias, not a redirect, because the bots call /api/next and urllib doesn't follow redirects.systemctl --user status massgowhere-bot).anselmlong/catholic-bot) has a Nearest Mass button that calls /api/next.| Surface | What you get |
|---|---|
| Find a Mass (home → answer) | The Mass you can still make, when to leave, and how long the trip takes. Below it: a map with the churches around you, each labelled with its next reachable Mass, then the other churches you can make it to, and the nearest church. Navigate hands off to Google Maps. |
| Plan ahead | "Leaving later": any departure time up to 7 days ahead. Narrow to Sunday or Sunset Mass, morning, lunchtime or evening, or a language. |
Catch a Mass on the way (#/way) | Going from A to B, maybe needing to be at B by a set time: which Mass adds the least to the trip. The answer is three times: leave by, Mass, reach B. |
Adoration / Confession (#/open) | An Adoration room that's open (or opens soon), or Confession you can get to. |
Church page (#/church/:id) | Photo, next Mass you can attend, this week's Masses by day, changes to the usual Masses, Adoration, Confession, this week's bulletin, parish details, and sources. |
All churches (#/churches) | Map of all 32 parishes, or a list with filters (day, time, language, distance, sort). |
| Telegram bot | The same answer as the site: share your location or send a postal code. Travel-mode buttons, a "Sunday or Sunset Mass" button, "Catch a Mass on the way", /feedback. |
┌──────────────────────── data pipeline (VPS, cron) ─────────────────────────┐
myCatholicSG Firestore ──┤ update_mycatholic.sh → import_mycatholic.py → data/mycatholic.json │
(prod-sg, daily) │ │
bulletins ───────────────┤ update_bulletins.sh → fetch_bulletins.py → scrape_parishes.py │
(weekly, Sat 9pm SGT) │ --bulletin-only (LLM) → data/parishes/<id>.json ├─→ build_data.py ─→ public/data.json
hand-curated ────────────┤ data/rich/<id>.json (reviewed read + hand checks) · data/services.json │ public/parish/<id>.json
(by hand) │ │
MOM public holidays ─────┘ data/holidays.json ┘
│ git commit + push to main
▼
Vercel (static public/ + serverless api/)
┌──────────────────────────────────────────────────────────────┐
browser (SPA) ─────────┤ public/index.html, app.js, style.css │
│ shares the same │ public/schedule.js · rank.js · services.js · way.js ◄────────┼── also required by api/*
│ ranking code │ api/next.js api/way.js api/feedback.js │
▼ └───────────────┬──────────────────────────────┬───────────────┘
MapLibre + OpenFreeMap tiles │ lib/onemap.js │ Telegram sendMessage
▼ ▼
OneMap routing (SLA) owner's Telegram (feedback)
Telegram bot (bot/bot.py, VPS) ──── GET /api/next, /api/way ────┘
Gospel bot (catholic-bot) ──── GET /api/next
Design principles
public/rank.js (plain JS, no build step). The browser loads it, and the API requires it. The bots never rank; they call the API. So the site, the bot and the gospel bot always agree.fast=1, a few ms) and the live answer together. It waits up to 2.5 s (3 s for "on the way") for live, so most answers appear once. If live is slow, the estimate shows first and the live times settle in place without a repaint.data.json and distance estimates.public/)#/, #/next, #/church/:id, #/churches, #/way, #/open. One stylesheet. Font: Atkinson Hyperlegible Next (Google Fonts). Vercel Web Analytics records hash-route pageviews.cooperativeGestures).?preview=1.#/next):
season() in app.js); the header shows the season, e.g. "Ordinary Time". Fills, not outlines. 44px tap targets. Reduced-motion respected.app.js (UI, ~2,000 lines), schedule.js (MassSchedule), rank.js (MassRank), services.js (MassServices), way.js (), (schedule snapshot), (church-page details, fetched per page), + (144px squares) + .api/, Vercel Node functions)All endpoints return JSON with Cache-Control: no-store.
GET /api/next: the Mass you can make.
| Param | Values | Default |
|---|---|---|
lat, lng | a point in Singapore (rough box: 1.15–1.475, 103.59–104.1) | required; 400 outside |
mode | transit | drive | walk | transit |
at | epoch ms or ISO time, up to 7 days ahead | now |
part | morning (before noon) | lunch (noon–3pm) | evening (from 3pm) | any |
sunday | 1: Sunday, or Saturday from 4pm (Sunset Mass) | off |
lang | e.g. Mandarin, Tagalog | any |
late | up to 15: count a Mass you'd walk into at most this many minutes late | 0 |
fast | 1: distance estimates only, no OneMap (instant first frame) | off |
kind | adoration | confession: an open Adoration room or Confession instead | Mass |
Response:
best, alternatives[≤3], and each of those has:
parish{id,name,address,postal,lat,lng}, start, leaveBy, language, location, notetravelMin, travelSource (onemap or estimate), walk, lateMin, distanceKmnearest{parish,travelMin,walk,next}around[≤5]: the closest churches by distance, each with its next reachable Mass, for the map.specialDay, dataAsOf, and the inputs echoed back.kind: best, alternatives, checked, and unconfirmed (parishes whose times aren't clear).GET /api/way?from=lat,lng&to=lat,lng&mode=…[&at=…][&by=…][&late=15][&fast=1]: a Mass on the way.
best, alternatives, direct. Each stop has start, end, leaveBy, arrive, toMin, onwardMin, detourMin, lateMin.GET /api/search?q=…: place search (postal code, MRT station, building, street) through OneMap from our servers, with our OneMap token.
results[≤5] of {name, address, postal, lat, lng}, cached at the edge for a day.502 means OneMap couldn't be reached, which is different from an empty result.POST /api/feedback {message, contact?, page?}: forwards the message to the owner on Telegram.
public/rank.js, services.js, way.js)Next Mass (rank)
at), travel, and arrive BUFFER_MIN (5) minutes before the start. Trips over MAX_TRIP_MIN (75) are never suggested.WINDOW_MIN (90) of S, the shortest trip wins; the earlier start breaks ties.late=15): the window shrinks to LATE_WINDOW_MIN (30), and the least-late Mass wins, then the shortest trip. This is hidden in the UI for now (LATE_UI = false).Routing
MAX_ROUTED.tripMinutesWithin, lib/onemap.js); a slower one falls back to the estimate for that church, so one slow route can't hold up the answer.preferWalk).Adoration / Confession (services.js)
data/services.json (hand-checked).On the way (way.js)
by, still reach B in time.late, each late minute counts double). Without a deadline, only Masses within 120 min of the earliest one that fits count.Schedule expansion (schedule.js)
isSunset, forSunday, and the time-of-day PARTS.scripts/, VPS)update_mycatholic.sh reads the Firestore database prod-sg, the one mycatholic.sg itself reads. The (default) database is an old copy that stopped updating on 3 July 2026.import_mycatholic.py turns the export into weekly, nth-week and last-week rules plus dated additions and cancellations.data/rich/<id>.json, the reviewed read plus what Anselm checks by hand, and data/services.json for the Adoration/Confession finder.fetch_bulletins.py takes each parish's newest bulletin from myCatholicSG (prod-sg, collection bulletin; a PDF). Failing that, it finds the bulletin on the page data/bulletins.json names: the newest dated link, a PDF under a "bulletin" heading, a Drive file in an embedded app, or an emailed newsletter (St Michael's). St Mary of the Angels publishes no bulletin: its website announcements (a Squarespace page, read as JSON with ?format=json) stand in, the last 3 weeks of posts as the text, and its card says "parish announcements".scrape_parishes.py --bulletin-only --only <ids>), for one-off Mass changes (dated), events and parish info. Its regular Mass list isn't used.data/rich/<id>.json; first imported with import_rich.py from the reviewed 29 Sept 2026 read, then edited by hand):
bot/bot.py)MASSGOWHERE_API (default https://massgowhere.com) for every answer./feedback: forwards to the owner. It also asks for feedback once after a person's 3rd and 15th answer./stats (admins only): daily counts, with people as salted hashes.bot/state.json, nudges.json, stats.json, feedback.log (all git-ignored on the VPS).| Runtime | Vercel static hosting (public/) + Node serverless functions (api/); no build step (vercel.json: outputDirectory: public, no framework) |
| Client | Vanilla ES2020+, no dependencies. MapLibre GL 5.24 loaded on demand. Uses :has(), <dialog>, IntersectionObserver, color-mix(); current Safari / Chrome / Firefox |
| Routing provider | OneMap (Singapore Land Authority): /api/public/routingsvc/route. Token from email + password, cached ~72 h; one shared in-flight login; retried on 401. Route cache in memory, keyed by origin (3 dp), destination, mode and 15-minute departure slot |
| Map tiles | OpenFreeMap "liberty" (no key) |
| Data size | data.json holds 32 parishes with rules, dated changes, services, holidays and bulletins; parish/<id>.json is fetched only by the church page; thumbnails ~8 KB each |
| Timezone | All times are epoch ms / ISO UTC; Singapore time is UTC+8 with no DST (SGT_OFFSET_MS) |
| Analytics | Vercel Web Analytics (cookieless) on the site; /stats in the bot |
| Privacy | No accounts. Location is used in the browser and sent to /api/next per request, never stored. Recent places live in the browser's localStorage only |
| Bot runtime | Python 3, standard library only; systemd user service on the VPS |
| Data scripts | Python 3, standard library (urllib); LLM reads via OpenRouter (default deepseek/deepseek-v4.1-flash); Playwright for JS sites; flock locking; scripts reset to origin/main and commit only regenerated files |
| Tests | node --test test/*.test.js: schedule expansion, ranking, services, on-the-way, OneMap parsing, API, parish text |
| Constant | Value | Where |
|---|---|---|
| Arrive early | 5 min | rank.js BUFFER_MIN |
| Answer window | 90 min (30 when "a bit late") | rank.js WINDOW_MIN, LATE_WINDOW_MIN |
| Longest trip suggested | 75 min | rank.js MAX_TRIP_MIN |
| Walk instead of bus | ≤10 min, or ≤15 min if no slower | rank.js SHORT_WALK_MIN, MAX_WALK_MIN |
| A bit late | ≤15 min | rank.js MAX_LATE_MIN |
| Search horizon | 2 days (7 for Sunday or a language; 7 for Confession) | api/next.js, services.js |
| Plan ahead | up to 7 days | api/next.js PLAN_DAYS |
| Sunset Mass | Saturday from 4pm | schedule.js SUNDAY_EVE_MIN |
| Rush-hour driving | ×1.4, weekdays 7:30–9:30am, 5:30–8pm | lib/onemap.js |
| Wait for live answer | 2.5 s site, 3 s bot and "on the way" | app.js, bot.py |
cp -n .env.example .env # -n: never overwrite an existing .env
node scripts/dev.js # http://localhost:8787 (site + /api/*; reads .env literally)
node --test test/*.test.js # all tests
python3 bot/bot.py # Telegram bot (uses the live API; MASSGOWHERE_API overrides)
Without OneMap credentials, the API answers with distance estimates. Add ?preview=1 for the design options (home layouts, colours, fonts); the choice is remembered per browser.
| Variable | Where | For |
|---|---|---|
ONEMAP_EMAIL, ONEMAP_PASSWORD | Vercel (production + preview), .env | routing |
TELEGRAM_BOT_TOKEN | Vercel (for /api/feedback), VPS .env (bot) | feedback, bot |
FEEDBACK_CHAT_ID | optional, Vercel / VPS | where feedback goes (default: the owner) |
ADMIN_CHAT_IDS | optional, VPS | who can use /stats |
MASSGOWHERE_API | optional, VPS | the site the bot calls |
OPENROUTER_API_KEY, OPENROUTER_MODELS | VPS / Mac .env | bulletin reads |
| What | How | Schedule |
|---|---|---|
| myCatholicSG times | sh scripts/update_mycatholic.sh (fetch, import, build, commit, push) | VPS cron, daily 04:00 UTC (12:00 SGT); ~/mgw.log |
| This week's bulletins | sh scripts/update_bulletins.sh | VPS cron, weekly, Saturday 13:00 UTC (9pm SGT); ~/mgw-bulletins.log |
| Church photos | scripts/fetch_photos.py, scripts/fetch_mycatholic_photos.py | by hand |
| Public holidays | edit data/holidays.json when MOM publishes next year's list | build warns at < 60 days |
| Bot changes | git pull on the VPS, then systemctl --user restart massgowhere-bot | after any change to bot/bot.py |
# cron, to set up again on a new machine
(crontab -l 2>/dev/null; echo '0 4 * * * sh $HOME/massgowhere/scripts/update_mycatholic.sh >>$HOME/mgw.log 2>&1') | crontab -
(crontab -l 2>/dev/null; echo '0 13 * * 6 sh $HOME/massgowhere/scripts/update_bulletins.sh >>$HOME/mgw-bulletins.log 2>&1') | crontab -
# health
systemctl --user status massgowhere-bot
tail ~/mgw.log ~/mgw-bulletins.log
api/ next.js · way.js · feedback.js Vercel functions
lib/ onemap.js OneMap auth, routing, cache, walk-vs-bus
public/ index.html · app.js · style.css the site
schedule.js · rank.js · services.js · way.js shared logic (browser + API)
data.json · parish/<id>.json · photos/ generated data and photos
bot/ bot.py Telegram bot
scripts/ update_mycatholic.sh · update_bulletins.sh cron entry points
import_*.py · scrape_parishes.py · fetch_*.py · build_data.py · dev.js
data/ mycatholic.json · services.json · holidays.json · sources.json · bulletins*.json
parishes/<id>.json (bulletin reads) · rich/<id>.json (hand-curated)
test/ node:test suites
LATE_UI = false).MassWaydata.jsonparish/<id>.jsonphotos/<id>.jpgphotos/thumb/<id>.jpgphotos/credits.jsonbuild_data.py writes public/data.json (all parishes, rules, dated changes, services, holidays, bulletin per parish) and public/parish/<id>.json. It warns:
FINDER GAP line for each church whose page mentions Adoration or Confession that data/services.json doesn't cover (or marks unconfirmed), so it can be added by hand.origin/main, so a changed script never runs its old code once.public/photos/credits.json; each photo is self-hosted with a 144px thumbnail.fetch_photos.py, fetch_mycatholic_photos.py (macOS sips), find_*.