mirror of
https://github.com/tiennm99/tiennm99bot.git
synced 2026-10-11 03:13:46 +00:00
/wordledaily sends a BotFather game card whose page plays one shared word per day, changing at 07:00 ICT. The server checks guesses, saves each player's board after every guess and keeps NYT-style stats. A win is reported with setGameScore, and in a group one live results message shows everyone's colour grids. /wordledaily_subscribe and /wordledaily_unsubscribe opt a chat or topic in to the 07:00 push: yesterday's recap, opening with the answer and the group's win streak, then today's card. The HTML5 game plumbing that noitu used is now shared in util/htmlgame, and the wordle word list and scoring live in wordle/wordlist.
100 lines
5.7 KiB
YAML
100 lines
5.7 KiB
YAML
services:
|
|
bot:
|
|
build:
|
|
context: .
|
|
# Or pin a prebuilt image instead of building:
|
|
# image: ghcr.io/tiennm99/tiennm99bot:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
# --- Required ---
|
|
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN} # from @BotFather — SECRET
|
|
MONGO_URL: ${MONGO_URL} # Atlas SRV string incl. credentials — SECRET
|
|
MONGO_DATABASE: ${MONGO_DATABASE} # e.g. tiennm99bot
|
|
|
|
# --- Optional (commented out; the code falls back to each default) ---
|
|
# Coolify injects every dashboard variable into each service through its
|
|
# generated .env (env_file), so setting one in the Coolify dashboard is
|
|
# enough — no need to uncomment. Coolify also creates a dashboard entry
|
|
# for every variable referenced here, and an existing empty entry beats
|
|
# the compose default, so these stay commented to keep that from
|
|
# happening. Outside Coolify, uncomment a line to pass the variable in.
|
|
# MODULES: ${MODULES:-} # CSV of modules; empty = all modules
|
|
# OWNER_ID: ${OWNER_ID:-} # Telegram user id for owner-only commands; empty = none
|
|
# ADMIN_IDS: ${ADMIN_IDS:-} # CSV of admin Telegram user ids; empty = none
|
|
# LOL_PANDASCORE_TOKEN: ${LOL_PANDASCORE_TOKEN:-} # PandaScore token — SECRET; empty = /lol* fetches fail
|
|
# GOLD_VNAPP_API_KEY: ${GOLD_VNAPP_API_KEY:-} # VNAppMob key — SECRET; empty = fetched and cached in Mongo
|
|
# BOT_USERNAME: ${BOT_USERNAME:-} # bot username without "@"; empty = getMe at startup
|
|
# STICKER_PACK_NAME: ${STICKER_PACK_NAME:-} # /addsticker set; empty = stickers_by_<bot username>
|
|
# GAME_BASE_URL: ${GAME_BASE_URL:-} # public https base routed to this service's :8080; empty = noitu and wordledaily games disabled
|
|
# NOITU_GAME_SECRET: ${NOITU_GAME_SECRET:-} # >= 32 bytes, signs game links — SECRET; empty = derived from the bot token
|
|
# WORDLEDAILY_GAME_SECRET: ${WORDLEDAILY_GAME_SECRET:-} # >= 32 bytes, signs wordledaily links and keys its answers — SECRET; empty = derived from the bot token
|
|
# LOG_LEVEL: ${LOG_LEVEL:-info} # debug|info|warn|error
|
|
|
|
# --- Fixed by this stack (not Coolify settings) ---
|
|
# The bundled renderer service below draws /wheelofnames, /gacha and
|
|
# /genshin. Its base URL is fixed to the in-network address so a stale
|
|
# platform-level value cannot point the bot elsewhere; the bot appends
|
|
# each /api/<name> route itself.
|
|
RENDERER_URL: http://renderer:3000
|
|
|
|
# --- Deliberately not declared ---
|
|
# SOURCE_COMMIT: Coolify provides it at runtime via its generated .env;
|
|
# declaring it here could override that with an empty value.
|
|
# PORT: the health server defaults to 8080, which the healthcheck uses.
|
|
# KV_PROVIDER: storage auto-selects mongodb because MONGO_URL is set.
|
|
# Long polling = no TELEGRAM_WEBHOOK_SECRET, no /webhook. A public domain
|
|
# is needed only for the noitu game (GAME_BASE_URL).
|
|
# Cron is in-process only — no CRON_MODE, no /cron route, no secret.
|
|
# No stock/coin/gold URL overrides — modules use their coded default
|
|
# providers (stock: SSI/VCI/KBS; coin: Binance->Coinbase->CoinGecko;
|
|
# gold: VNAppMob).
|
|
# Long polling is outbound-only, so Telegram itself needs no inbound route
|
|
# and no host port is ever published. `expose` documents port 8080 (health,
|
|
# plus the noitu game page when GAME_BASE_URL is set). Attach a Coolify
|
|
# domain to this service on port 8080 only when GAME_BASE_URL is set; the
|
|
# game routes do not exist otherwise.
|
|
expose:
|
|
- "8080"
|
|
# Coolify's UI health check does not apply to Docker Compose apps; it reads
|
|
# this block instead. GET / returns text/plain "tiennm99bot ok". It proves the
|
|
# process is serving, not that Mongo is reachable — see
|
|
# docs/deploy-coolify-selfhosted.md. busybox wget ships in the Alpine image.
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:8080/ || exit 1"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 20s
|
|
|
|
# Animation renderer (Node + Remotion + headless Chrome) from renderer/.
|
|
# Internal only: the bot reaches it over the compose network, so it has no
|
|
# published port, no public domain, and no auth token. Never publish a port
|
|
# or attach a domain to it — its API is unauthenticated.
|
|
renderer:
|
|
build:
|
|
context: ./renderer
|
|
restart: unless-stopped
|
|
environment:
|
|
# Every renderer setting carries the RENDERER_ prefix so it reads as a
|
|
# renderer setting and cannot clash with the bot's variables.
|
|
|
|
# --- Optional tuning (commented out; the renderer falls back to each default) ---
|
|
# Set any of these in the Coolify dashboard; it reaches this service
|
|
# through Coolify's injected .env. Outside Coolify, uncomment a line.
|
|
# RENDERER_MAX_CONCURRENT_RENDERS: ${RENDERER_MAX_CONCURRENT_RENDERS:-1}
|
|
# RENDERER_RENDER_TIMEOUT_MS: ${RENDERER_RENDER_TIMEOUT_MS:-30000} # floor 7000
|
|
# RENDERER_MAX_OPTIONS: ${RENDERER_MAX_OPTIONS:-32} # wheel options
|
|
# RENDERER_MAX_OPTION_CHARS: ${RENDERER_MAX_OPTION_CHARS:-40} # chars per option/label
|
|
|
|
# --- Fixed by this stack (not Coolify settings) ---
|
|
NODE_ENV: production
|
|
RENDERER_HOST: 0.0.0.0
|
|
RENDERER_PORT: "3000" # keep in sync with the bot's RENDERER_URL and the healthcheck
|
|
expose:
|
|
- "3000"
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "node -e \"fetch('http://127.0.0.1:3000/api/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\""]
|
|
interval: 30s
|
|
timeout: 5s
|
|
retries: 3
|