MyCdA
자전거용 무료 CdA 계산기, 파워미터 현장 테스트와 Chung 가상 고도법으로 항력 면적·구름 저항을 측정
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Web App (PWA) ธีม Sporty Neon-Lime & Sleek Graphite-Black สำหรับจดสมาชิก นับลูก คำนวณค่าใช้จ่าย และจัดคิวเกมแบดมินตัน Real-time sync ทุกคนเห็นพร้อมกัน — ใช้ฟรี 100%
เวอร์ชันปัจจุบัน: 3.19.0 (cache badcount-v55)
🌐 Live: badcount.vercel.app
📦 GitHub: leezen441/Badcount
#b5f714) และดำกราไฟต์ (#0d0e12) — initTheme() ใส่คลาส dark ตลอด ไม่มีโหมดสว่าง#121315) บนปุ่มเขียวนีออน อ่านง่ายภายใต้แสงสนามsettings/defaults (lastUsedCourts)1-5, 8) หรือโหมดนับจำนวนอย่างง่าย (simpleShuttleCount) — ลูกจากแมตช์หารให้ผู้เล่นในเกมนั้น (ยกเว้นคนที่ถูก exempt)manualFee ให้แอดมินใส่ยอดคงที่รายคนrounded-2xlbadcount-v33 ใน sw.js — เคลียร์แคชเก่าเมื่ออัปเดตpremium.css?v=3.16.0 และ app.js?v=3.16.0 ใน index.html
app.js / premium.css ต้อง bump ?v= ใน index.html และ CACHE_NAME ใน sw.js ไม่งั้นผู้ใช้ไม่เห็นของใหม่settings/defaults (canonical EMV + quiet zone) — fallback เป็น session.bankQR ถ้ายังไม่มี PromptPay IDsessions/{id}/receipts/{memberId} เป็น base64 ใน Firestore (ไม่ใช้ Firebase Storage) พร้อม metadata ลบอัตโนมัติ ~30 วันPASSCODE_HASH ใน app.js — จำใน localStorage 30 วันapp.js (ข้อความตรง ไม่ได้ hash) — จำจนกว่าจะออกจากระบบ/#/m/{id}isPaused: true จนแอดมินปลดเมื่อมาถึงสนามHash router ใน app.js (route()):
| URL | สิทธิ์ | หน้าที่ |
|---|---|---|
/#/ | Admin | แดชบอร์ด สร้าง/เปิดก๊วน |
/#/history | Admin (Manager ถูกส่งไป m-home) | ประวัติกลุ่ม 50 รายการล่าสุด |
/#/personal-stats | Admin & Manager | สถิติรายคน จำนวนเกม ค่าลูก ยอดเงิน ตามช่วงเวลา |
/#/admin-summary | Admin & Manager | สรุปยอดรับ จ่าย ค้าง |
/#/session/{id} | Admin (ไม่ล็อกอินจะล็อก nav) | จัดการก๊วนเต็มสิทธิ์: สมาชิก สนาม เงิน จัดเกมนับลูก |
/#/m-home | Manager / Admin | รายการก๊วนล่าสุดสำหรับผู้จัดการ |
/#/m/{id} | Temp Manager | จัดการก๊วนนี้ (ต้อง PIN 4 หลักของก๊วนนั้น ถ้ายังไม่ผ่าน) |
/#/join/{id} | Public | ลงชื่อ (พักคิวอัตโนมัติ · จำระดับมือ) ดูยอด อัปโหลดสลิป — เลือก buddy ได้เมื่อก๊วนเป็นโหมด advance |
บอท LINE Official Account ผ่าน Vercel Serverless Functions (api/) ต่อ Firestore โปรเจกต์เดียวกัน (badcount-a1296)
| พิมพ์ในแชต/กลุ่ม | ใครสั่งได้ | การทำงาน |
|---|---|---|
startbadcount | เจ้าของ | เริ่มทำงาน + จำแชต/กลุ่มนี้เป็นปลายทาง · คนแรกที่พิมพ์ = เจ้าของถาวร (Trust-On-First-Use) |
stopbadcount | เจ้าของ | หยุดทำงาน — คำสั่ง invite / close จะเงียบด้วย |
invite | ทุกคน | ตอบ list invite ของก๊วนที่เปิดอยู่ล่าสุด (ไม่มีก๊วนเปิด → แจ้งว่าไม่มี) |
close | ทุกคน / คนที่อนุญาต | ก๊วนล่าสุดที่วันที่ไม่เกินวันนี้ (ข้ามก๊วนล่วงหน้า) · ปิดแล้ว → ทุกคนได้ list จ่ายเงิน · ยังเปิด → เจ้าของหรือคนที่อนุญาต = ปิด Court ในแอปให้เลย + ตอบ list จ่ายเงิน · คนที่ยังไม่อนุญาต = ได้รหัส 4 หลักให้เจ้าของอนุมัติ |
allow <รหัส> | เจ้าของ | อนุญาตคนที่ขอรหัสนั้นให้ปิด Court ผ่าน close ได้ถาวร (รหัสหมดอายุ 30 นาที) |
closers | เจ้าของ | ดูรายชื่อคนที่ปิด Court ได้ |
disallow <ลำดับ> | เจ้าของ | ถอนสิทธิ์คนลำดับนั้นจาก closers |
Close, INVITE)invite / close ใช้ได้เฉพาะตอนบอท activesettings/lineBot.closers (เปลี่ยนชื่อในไลน์ก็ปลอมไม่ได้)startbadcountindex.html ข้างช่องวันที่) — handler ใน app.js และ api/line-notify.js ยังอยู่ครบ ลบคอมเมนต์ออกก็ใช้ได้ทันทีremindUnpaidViaLine() ใน app.js ยังเก็บไว้ ผูกกลับที่ listener ของ btnRemindUnpaid เพื่อใช้ใหม่LINE_CHANNEL_ACCESS_TOKEN, LINE_CHANNEL_SECRET (ตัวเลือก PUBLIC_BASE_URL ถ้าต้องการบังคับโดเมน)https://<โดเมน>/api/line-webhook → เปิด Use webhookstartbadcount (คนพิมพ์คนแรก = เจ้าของ)LINE API รันได้เมื่อ deploy บน Vercel (หรือ vercel dev) — สแตติกเซิร์ฟเวอร์อย่าง python -m http.server จะไม่รัน /api/*
settings/lineBot — adminUserId, active, groupId / roomId / directUserId, updatedAtlineNotifiedAt, lineOpenNotifiedAt (optional) — ไม่แตะ members / ค่าใช้จ่าย / สลิปบอทผูกกับ Firebase project ของ instance นั้น — ถ้าจะทำ Team B ต้องสร้าง channel + ตั้ง env แยก และชี้ firebase config ใน
api/_firebase.jsไปโปรเจกต์นั้น
Badcount/
├─ index.html ← UI หลัก (premium.css?v=3.16.0 / app.js?v=3.16.0)
├─ premium.css ← ธีมเขียวนีออน-ดำกราไฟต์
├─ app.js ← Logic หลักทั้งหมด (จัดคิว, คำนวณเงิน, PWA, join, LINE ฝั่งไคลเอนต์)
├─ firebase-config.js ← Firestore web config (โปรเจกต์ badcount-a1296)
├─ manifest.json ← PWA manifest
├─ icon.svg / icon-maskable.svg
├─ sw.js ← Service Worker (CACHE_NAME = badcount-v55)
├─ qrcode.min.js ← สร้าง PromptPay QR (local)
├─ package.json ← dependency `firebase` สำหรับ serverless เท่านั้น
├─ api/ ← LINE Bot — Vercel Serverless Functions
│ ├─ line-webhook.js ← startbadcount / stopbadcount
│ ├─ line-notify.js ← push invite / ทวงเงิน (เรียกจากปุ่มเท่านั้น)
│ ├─ _totals.js ← สูตรเงิน (ต้องตรงกับ calcSessionTotals ใน app.js)
│ └─ _firebase.js / _line.js / _sessions.js / _notify.js
├─ .gitignore
└─ README.md
ไม่มี bundler / TypeScript / ชุดทดสอบ — เปิด index.html คุยกับ Firestore โดยตรง
badcount</> (Web App)badcount-web → กด Register appfirebaseConfig ไปใส่ใน firebase-config.jsแอปนี้ ไม่ใช้ Firebase Authentication — ล็อกอินเป็น passcode ฝั่งไคลเอนต์ ไม่ต้องเปิด Auth
asia-southeast3 (Bangkok) (เปลี่ยนทีหลังไม่ได้)ใน Firestore → แท็บ Rules — rules ชุดที่ใช้อยู่เปิดอ่าน/เขียนได้ทุกคน (จำกัดขนาดสมาชิก ≤ 50):
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /sessions/{sessionId} {
allow read: if true;
allow create: if request.resource.data.keys().hasAll(['date', 'members', 'status'])
&& request.resource.data.members.size() <= 50;
allow update: if request.resource.data.members.size() <= 50;
allow delete: if true;
match /receipts/{memberId} {
allow read: if true;
allow write: if true;
}
}
match /settings/{docId} {
allow read: if true;
allow write: if true;
}
}
}
กด Publish
ผู้ที่มีลิงก์ก๊วนหรือ join URL อ่าน/แก้ข้อมูลได้ตาม rules ด้านบน — แชร์เฉพาะกลุ่มที่ไว้ใจ
firebase-config.jsแก้ไฟล์ firebase-config.js ในโฟลเดอร์โปรเจกต์:
export const firebaseConfig = {
apiKey: "AIza...",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project",
storageBucket: "your-project.appspot.com",
messagingSenderId: "123456789",
appId: "1:123456789:web:abc123"
};
ค่าเหล่านี้เป็น public client config ตามที่ Firebase ออกแบบไว้ ความคุมสิทธิ์อยู่ที่ Security Rules
ใน app.js หา PASSCODE_HASH แล้วนำรหัสที่ต้องการมา hash เป็น SHA-256
ใน Console ของเบราว์เซอร์ (F12):
const buf = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode("YOUR_PASSWORD")
);
console.log(
Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, "0"))
.join("")
);
วางผลลัพธ์ลง PASSCODE_HASH
รหัสผู้ช่วย (MANAGER_PASSCODE) ตั้งแยกในไฟล์เดียวกัน — เป็นข้อความตรง ไม่ได้ hash
รีโปที่ใช้อยู่แล้ว: https://github.com/leezen441/Badcount.git
ถ้าเริ่มรีโปใหม่:
git init
git add .
git commit -m "Initial BadCount App Setup"
git remote add origin https://github.com/USERNAME/badcount.git
git branch -M main
git push -u origin main
/api)https://your-app.vercel.app🎉 ส่งลิงก์ก๊วนให้สมาชิกลงชื่อและสแกนจ่ายได้ทันที
สูตรใน calcSessionTotals (app.js) และ api/_totals.js ต้องตรงกัน:
N = จำนวนสมาชิกในก๊วน
ค่าคอร์ตต่อคน = (ค่าคอร์ตรวม ÷ N) หรือ ราคาต่อคนคงที่ ตาม courtFeeType
ค่าอื่นๆ ต่อคน = (ค่ารวม ÷ N) หรือ ราคาต่อคนคงที่ ตาม otherCostType
ลูกของสมาชิก X =
shuttlesUsed (ใส่เอง) + ส่วนแบ่งลูกจากทุกแมตช์ที่ X ลง
(แมตช์หารให้ผู้เล่นในเกมนั้น; ถ้ามีคน exempt คนที่เหลือแบกส่วนนั้น)
ลูกที่คิดเงิน = 0 ถ้า excludeAllShuttles
หรือ max(0, ลูกของ X − shuttlesExcluded)
หรือตามปกติ
ยอด X = ค่าคอร์ตต่อคน + ค่าอื่นๆ ต่อคน + (ลูกที่คิดเงิน × shuttlePrice)
ถ้ามี manualFee → ใช้ยอดนั้นแทนทั้งสูตร
โหมดลูก:
1-5, 8 แล้วระบบนับจำนวนsimpleShuttleCount): ใส่ตัวเลขจำนวนลูกตรงๆก๊วน 4 คน ค่าคอร์ตรวม 400 · ค่าน้ำรวม 100 · ลูกละ 25 · A ใช้ 2, B 1, C 2, D 1
| สมาชิก | ค่าสนาม | ค่าน้ำ/อื่นๆ | ค่าลูก | ยอดสุทธิ |
|---|---|---|---|---|
| A | 100 ฿ | 25 ฿ | 50 ฿ (2 ลูก) | 175 ฿ |
| B | 100 ฿ | 25 ฿ | 25 ฿ (1 ลูก) | 150 ฿ |
| C | 100 ฿ | 25 ฿ | 50 ฿ (2 ลูก) | 175 ฿ |
| D | 100 ฿ | 25 ฿ | 25 ฿ (1 ลูก) | 150 ฿ |
| รวม | 400 ฿ | 100 ฿ | 150 ฿ | 650 ฿ |
ถ้าเปิดใน LINE in-app browser แอปจะพยายามเด้งออกไปบราวเซอร์จริง (openExternalBrowser=1) เพราะแชร์รูป/กล้องใน LINE มักไม่ครบ
แอปหลัก (สแตติก + Firestore):
python -m http.server 8000
เปิด http://localhost:8000
หรือ VS Code Live Server ที่ index.html
ต้องมีเน็ตสำหรับ Firebase + CDN (Tailwind, Fonts, jsQR)
LINE /api/*: ใช้ vercel dev หลัง npm install และใส่ env ของ LINE — http.server ไม่รัน serverless
| บริการ | Free tier ที่เกี่ยวข้อง | หมายเหตุของแอปนี้ |
|---|---|---|
| Vercel Hosting | bandwidth ตามแพลน Hobby | สแตติก + serverless LINE |
| Firestore (Spark) | reads / writes รายวัน | ฐานข้อมูลหลัก + สลิป base64 ใน subcollection receipts |
| LINE Messaging API | ตามโควตาช่องทาง | เลยตัดการส่งอัตโนมัติทั้งหมด เหลือแค่กดปุ่มเอง |
ไม่ใช้ Firebase Storage — รูปสลิปอยู่ใน Firestore document
| เลเยอร์ | ของที่ใช้ |
|---|---|
| Frontend | HTML + premium.css + Tailwind CDN + Vanilla JS (ไม่มี build) |
| Database | Firebase Firestore realtime (onSnapshot) |
| Hosting | Vercel (push GitHub → deploy) |
| Auth | Admin SHA-256 + Manager passcode + Temp PIN — ไม่ใช้ Firebase Auth |
| PWA | Manifest + Service Worker + Notification API |
| QR | qrcode.min.js (สร้าง PromptPay) + jsQR CDN (อ่านสลิป) |
| LINE | Messaging API ผ่าน api/*.js |
MANAGER_PASSCODE) เป็นข้อความตรงใน app.js — เปลี่ยนเองถ้าแชร์รีโปสาธารณะ/#/session/{id}, /#/m/{id} หรือ /#/join/{id} เข้าถึงข้อมูลก๊วนนั้นได้/api/line-notify ไม่มี auth ของผู้เรียก — ใครรู้ sessionId ตอนบอท active อาจทริกเกอร์โพสต์ LINE ได้โปรเจกต์นี้อยู่ภายใต้สัญญาอนุญาตสิทธิ์ MIT License
นำไปปรับแต่ง แก้ไข แจกจ่าย หรือพัฒนาต่อยอดในก๊วนของคุณได้โดยไม่ต้องขออนุญาต
🏸 BadCount — Count Every Game. Enjoy Every Match.