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.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 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.