Scorigami FC
Todos os placares finais da história do Brasileirão, das principais ligas europeias e de outras ligas das Américas em um gráfico: quais já aconteceram, quantas vezes e quais nunca aconteceram.
Translation pending
Wanna Tennis ? — find free tennis courts in Bangkok
Translation pending
Sports & fitnessJavaScript · CSSGitHub @FranceFlapjackOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Free tennis courts in Bangkok: live availability where a venue publishes it, a self-check
link where it doesn't, and a LINE bot that tells you when a court frees up.
Zero runtime dependencies — Node 22.5+ only (built-in node:sqlite).
| Website | LINE bot + alerts | Data freshness | |
|---|---|---|---|
Static site (GitHub Pages, .github/workflows/pages.yml) | ✅ | ❌ (buttons still open LINE with the command typed) | every ~15 min, often later |
Server (node server.js — your Mac + tunnel today, Fly.io later) | ✅ | ✅ | every 5 min |
The static site is the same page in "static mode": a scheduled GitHub job runs
lib/poll.js, then scripts/build-static.js puts the page, the pure lib/ modules and
data/snapshot.json into dist/, and the browser does the filtering (public/data.js).
Build it yourself: node lib/poll.js && node scripts/build-static.js.
node server.js
# open http://localhost:3000
npm test
29 offline tests (Node's built-in runner, no dependencies): the slot engine, every
display rule in lib/state.js (past hours, midnight rollover, failed checks,
indoor/outdoor, 🔔 window), change detection, and each adapter against canned
responses shaped like the real APIs — including a regression test for every bug found
so far. Run it before and after any change.
The server polls every venue on startup and then every 5 minutes.
Force a one-off refresh: node lib/poll.js
includeCourts / excludeCourts regex strings; default include is tennis court,
default exclude catches (Kid), U4, junior, mini.types: ['indoor','outdoor'] in the catalog.Commands (English or Thai): alert Sat 18-21 2h goat · แจ้งเตือน เสาร์ 18-21 2ชม ·
my alerts · cancel 2 · cancel all · link · help. Admin only: status, selfcheck.
LINE_BOT_BASIC_ID), and Alert my
group opens LINE's share picker. You tap Send; LINE proves who sent it.
watchToCommand + roundTrips guarantee the text reads back as the same alert.link → the bot replies with a one-tap link
(needs PUBLIC_URL; opens in the normal browser) or a 6-digit code. One use, 10 minutes,
wrong codes rate-limited. Same rules as chat (checkWatch).expectCourts.data/app.db (Node's built-in SQLite). Secrets: .env (see .env.example)..env with only a made-up LINE_CHANNEL_SECRET → dry-run;
every message goes to data/outbox.log. Chat with it: npm run line -- "alert sat 18-21 2h".lib/poll.js reads the previous data/snapshot.json before overwriting it and stamps
each currently-free (venue, date, court, hour) via computeFreed():
freedAt (no reset)freedAt = nowserver.js treats a stamp as fresh for FRESH_WINDOW_MS (30 min) and exposes fresh per
slot + hasFresh per venue/day. No database — the stamps live in the snapshot and survive
restarts. SQLite arrives only with the LINE per-user watch store.catalog.js is the only file you touch.
source: { type: 'hatch', base }source: { type: 'okrabook', base, detailPath, venueId, sportId }source: { type: 'reservationSystem', base, itemType: 'court' }source: { type: 'manual' } + a bookingUrl (or lineUrl)catalog.js venues + how to read each (the only file you edit to add courts)
adapters/
hatch.js bespoke public API (returns BOOKED -> we invert to free; UTC)
okrabook.js multi-tenant platform (returns FREE court ids per slot)
reservation-system.js SvelteKit platform, __data.json in devalue format (local time,
ships its own business hours + holidays). GOAT57 runs on it.
lib/
time.js Bangkok (UTC+7) date helpers + Thai labels
slots.js the engine: free hours -> qualifying starts for N-hour runs
poll.js walk catalog -> data/snapshot.json
lib/state.js pure view model: snapshot -> what the page shows ("now" injectable)
server.js node:http — static site + /api/venues, /api/state?minHours=&place=
public/ minimal frontend (index.html, styles.css, app.js) + Leaflet
data/snapshot.json latest poll (regenerated; safe to delete)
/availability/<type>/__data.json?date= → live adapter. Likely white-label; if another
venue runs it, it's a catalog row only. (GOAT57.)<slug>.okrabook.com) — multi-tenant, availability PUBLIC via
POST /venues/getTimes → live adapter. Real tenants redirect to
<slug>.okrabook.com/login; non-tenants bounce to okrabook.com. (Beat is one.)*-booking.kegroup.co.th) — phone/OTP login → self-check. (Crystal.)getoncourt.app) — nice public venue pages, but booking needs
Google/LINE/email login → self-check. Lists many BKK courts. (Simoorgh.)One Ubuntu VM (free shape E2.1.Micro or A1), Caddy for HTTPS, systemd keeps it running.
<ip-with-dashes>.sslip.io (e.g. 129-150-1-2.sslip.io).curl -fsSLO https://raw.githubusercontent.com/FranceFlapjack/wannatennis/main/deploy/oracle-setup.sh && bash oracle-setup.sh <hostname>.env and data/app.db to /opt/wannatennis/ (owner
wannatennis, .env mode 600) and sudo systemctl restart wannatennis.https://<hostname>/line/webhook, Verify. Logs: journalctl -u wannatennis -f.
Update later: rerun step 3 (it pulls the latest code and restarts).
Only one copy may run — two bots would push every alert twice.server.js to Fly.io (Dockerfile + fly.toml are ready;
secrets via fly secrets import < .env, data on a volume at /app/data), point the LINE
webhook at https://<app>.fly.dev/line/webhook, stop the Mac copy. ~US$3–5/month.Heads-up: GitHub pauses scheduled jobs in a public repo after 60 days without commits — re-enable in the Actions tab (or push anything).
@lottiefiles/dotlottie-wc.public/motion.js — optional,
off for reduced motion or ?motion=0..env, webhook through a Cloudflare quick tunnel whose
address changes on restart — fixed by the Fly.io step). Not built: AI free-text box.