From 8e748c31537384585310e436ca3d466a440fee10 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 7 Jul 2026 10:19:51 +0700 Subject: [PATCH] docs(plans): remove plan artifacts --- .../phase-01-implement-command.md | 86 ------ .../phase-02-update-command-surfaces.md | 54 ---- .../phase-03-validate-behavior.md | 68 ----- .../plan.md | 63 ----- .../pm-260703-0408-wheelofnames-complete.md | 34 --- .../phase-01-api-client-and-env-config.md | 114 -------- ...ase-02-command-integration-and-fallback.md | 105 ------- ...ase-03-deployment-docs-and-env-surfaces.md | 88 ------ ...hase-04-validation-and-regression-tests.md | 84 ------ .../plan.md | 161 ----------- ...0701-1000-mongodb-stats-design-research.md | 265 ------------------ ...0706-1134-wheelofnamesbeta-gif-research.md | 151 ---------- 12 files changed, 1273 deletions(-) delete mode 100644 plans/260703-0404-wheelofnames-misc-command/phase-01-implement-command.md delete mode 100644 plans/260703-0404-wheelofnames-misc-command/phase-02-update-command-surfaces.md delete mode 100644 plans/260703-0404-wheelofnames-misc-command/phase-03-validate-behavior.md delete mode 100644 plans/260703-0404-wheelofnames-misc-command/plan.md delete mode 100644 plans/260703-0404-wheelofnames-misc-command/reports/pm-260703-0408-wheelofnames-complete.md delete mode 100644 plans/260706-2220-wheelofnamesbeta-api-integration/phase-01-api-client-and-env-config.md delete mode 100644 plans/260706-2220-wheelofnamesbeta-api-integration/phase-02-command-integration-and-fallback.md delete mode 100644 plans/260706-2220-wheelofnamesbeta-api-integration/phase-03-deployment-docs-and-env-surfaces.md delete mode 100644 plans/260706-2220-wheelofnamesbeta-api-integration/phase-04-validation-and-regression-tests.md delete mode 100644 plans/260706-2220-wheelofnamesbeta-api-integration/plan.md delete mode 100644 plans/reports/260701-1000-mongodb-stats-design-research.md delete mode 100644 plans/reports/260706-1134-wheelofnamesbeta-gif-research.md diff --git a/plans/260703-0404-wheelofnames-misc-command/phase-01-implement-command.md b/plans/260703-0404-wheelofnames-misc-command/phase-01-implement-command.md deleted file mode 100644 index 6d42833..0000000 --- a/plans/260703-0404-wheelofnames-misc-command/phase-01-implement-command.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -phase: 1 -title: Implement command -status: completed -priority: P2 -dependencies: [] ---- - -# Phase 1: Implement command - -## Overview - -Add the `/wheelofnames` command inside `internal/modules/misc/misc.go`, using the current misc module pattern: small command factory returning `modules.Command`, `chathelper.ArgAfterCommand` for parsing, and `chathelper.Reply` for plain text response. - -## Requirements - -- Functional: parse text after `/wheelofnames` as comma-separated options. -- Functional: trim whitespace and drop empty options. -- Functional: reply usage when no valid options exist. -- Functional: randomly pick one option from valid entries; preserve duplicates as weighting. -- Non-functional: keep implementation local to misc; no storage dependency; no new module. - -## Architecture - -Flow: - -```text -Telegram update - -> dispatcher matches wheelofnames - -> wheelOfNamesCommand handler - -> chathelper.ArgAfterCommand - -> splitWheelOptions - -> math/rand/v2 rand.N(len(options)) - -> chathelper.Reply -``` - -Use a tiny pure helper for parsing: - -```go -func splitWheelOptions(arg string) []string { - parts := strings.Split(arg, ",") - out := make([]string, 0, len(parts)) - for _, part := range parts { - if s := strings.TrimSpace(part); s != "" { - out = append(out, s) - } - } - return out -} -``` - -No crypto-grade randomness required; this is a casual selection command, not auth, payment, or security behavior. - -## Related Code Files - -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/misc.go` -- Create: none -- Delete: none - -## Implementation Steps - -1. Add `math/rand/v2` import, unless existing Go version/toolchain rejects it; fallback to standard `math/rand` only if needed. -2. Update package comment to include `/wheelofnames`. -3. Add `wheelOfNamesUsage` constant, e.g. `Usage: /wheelofnames , , ...`. -4. Add `wheelOfNamesCommand()` returning public `modules.Command`. -5. Add command to `New()` after other public misc commands or near `/ping`. -6. Add `splitWheelOptions(arg string) []string` helper near the command. -7. Handler: - - return nil for nil message - - parse args with `chathelper.ArgAfterCommand(update.Message.Text)` - - reply usage if parsed options length is zero - - pick `options[rand.N(len(options))]` - - reply selected option as plain text - -## Success Criteria - -- [x] `misc.New` registers `wheelofnames` as public command. -- [x] Handler replies usage for missing/empty option list. -- [x] Handler never returns an empty option. -- [x] Handler uses plain text reply, preserving forum topic behavior through `chathelper.Reply`. - -## Risk Assessment - -Main risk: flaky tests if randomness is asserted too strictly. Mitigate by testing deterministic one-option input and set membership for multi-option input. - -Security: no HTML parse mode; user-supplied option text is plain text, so no HTML escaping needed. diff --git a/plans/260703-0404-wheelofnames-misc-command/phase-02-update-command-surfaces.md b/plans/260703-0404-wheelofnames-misc-command/phase-02-update-command-surfaces.md deleted file mode 100644 index 088ccf4..0000000 --- a/plans/260703-0404-wheelofnames-misc-command/phase-02-update-command-surfaces.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -phase: 2 -title: Update command surfaces -status: completed -priority: P2 -dependencies: - - 1 ---- - -# Phase 2: Update command surfaces - -## Overview - -Update all user-facing command surfaces required by `AGENTS.md` for a new Telegram command. This is an additive command, so no stats migration is needed. - -## Requirements - -- Functional: command appears in `/help` because it is public and registered in the misc module. -- Functional: command appears in `telegram-commands.json` for Telegram command menu setup. -- Functional: README misc module summary reflects the new command. -- Non-functional: no command rename/delete, so preserve stats behavior by doing nothing special. - -## Architecture - -The source of runtime behavior is `internal/modules/misc/misc.go`. `telegram-commands.json` is a manual Telegram command menu source, and `README.md` is the human-facing module overview. - -Stats compatibility note: -- Adding `/wheelofnames` creates new command usage rows naturally when the stats hook records invocations. -- No existing stats rows need migration because no command is renamed or deleted. - -## Related Code Files - -- Modify: `/config/workspace/tiennm99/miti99bot/telegram-commands.json` -- Modify: `/config/workspace/tiennm99/miti99bot/README.md` -- Create: none -- Delete: none - -## Implementation Steps - -1. Add command entry to `telegram-commands.json` near other misc commands: - - `command`: `wheelofnames` - - `description`: concise public description, e.g. `Pick one random comma-separated option` -2. Update README Modules table for `misc` to include `/wheelofnames`. -3. Do not update stats migrations or system state; this is add-only behavior. - -## Success Criteria - -- [x] `telegram-commands.json` remains valid JSON. -- [x] README `misc` command list includes `/wheelofnames`. -- [x] No migration or legacy stats marker added. - -## Risk Assessment - -Risk: runtime registration and manual command menu source drift. Mitigate by updating both `misc.go` and `telegram-commands.json` in same implementation. diff --git a/plans/260703-0404-wheelofnames-misc-command/phase-03-validate-behavior.md b/plans/260703-0404-wheelofnames-misc-command/phase-03-validate-behavior.md deleted file mode 100644 index a2cd48f..0000000 --- a/plans/260703-0404-wheelofnames-misc-command/phase-03-validate-behavior.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -phase: 3 -title: Validate behavior -status: completed -priority: P2 -dependencies: - - 1 - - 2 ---- - -# Phase 3: Validate behavior - -## Overview - -Add focused tests for the new handler and run command-surface validation required for command changes. - -## Requirements - -- Functional: tests cover registration, usage reply, one-option selection, trimming, and empty segment handling. -- Non-functional: run focused tests first, then full suite and vet because command/menu/shared behavior changed. - -## Architecture - -Use existing misc test harness: -- `installMisc` in `internal/modules/misc/handlers_test.go` wires the misc module to `testutil.RecordingBot`. -- `TestNew_RegistersExpectedCommands` in `internal/modules/misc/misc_test.go` locks command names and visibility. - -Avoid brittle randomness assertions: -- Single-option input should return that option exactly. -- Multi-option input should assert the reply is one of the non-empty trimmed options. - -## Related Code Files - -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/misc_test.go` -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/handlers_test.go` -- Create: none -- Delete: none - -## Implementation Steps - -1. Update `TestNew_RegistersExpectedCommands` expected map: - - add `wheelofnames: modules.VisibilityPublic` -2. Add handler tests: - - `/wheelofnames` returns usage. - - `/wheelofnames , ,` returns usage. - - `/wheelofnames Alice` returns `Alice`. - - `/wheelofnames Alice, Bob, Carol` returns one of those exact strings. - - `/wheelofnames , Alice , , Bob ,` returns `Alice` or `Bob`, never empty/spaced. -3. Run formatting and focused tests: - - `gofmt -w internal/modules/misc/misc.go internal/modules/misc/misc_test.go internal/modules/misc/handlers_test.go` - - `go test ./internal/modules/misc` -4. Run broader validation because command menu/user-facing contract changes: - - `go test ./cmd/server ./internal/modules/misc` - - `go test ./...` - - `go vet ./...` - -## Success Criteria - -- [x] Focused misc tests pass. -- [x] Command menu/server tests pass. -- [x] Full `go test ./...` passes. -- [x] Full `go vet ./...` passes. - -## Risk Assessment - -Risk: random selection makes tests nondeterministic. Mitigate by avoiding exact expected value for multi-option tests. - -Rollback: remove `wheelOfNamesCommand`, helper, tests, README entry, and `telegram-commands.json` entry. diff --git a/plans/260703-0404-wheelofnames-misc-command/plan.md b/plans/260703-0404-wheelofnames-misc-command/plan.md deleted file mode 100644 index 39d626f..0000000 --- a/plans/260703-0404-wheelofnames-misc-command/plan.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Wheel of Names Misc Command -description: >- - Add public /wheelofnames command to pick one random comma-separated option in - the misc module. -status: completed -priority: P2 -branch: main -tags: - - feature - - backend -blockedBy: [] -blocks: [] -created: '2026-07-03T04:04:35.848Z' -createdBy: 'ck:plan' -source: skill ---- - -# Wheel of Names Misc Command - -## Overview - -Add `/wheelofnames` to the existing `misc` module. The command accepts comma-separated options after the command text, trims whitespace, ignores empty entries, then replies with one randomly selected option. No storage, migrations, auth changes, or new module needed. - -Assumptions: -- Public command, visible in `/help` and Telegram command menu. -- Usage reply when no valid comma-separated entries remain. -- One valid entry is allowed and returns that entry. -- Duplicate entries stay duplicated, so users can weight an option intentionally. - -## Phases - -| Phase | Name | Status | -|-------|------|--------| -| 1 | [Implement command](./phase-01-implement-command.md) | Completed | -| 2 | [Update command surfaces](./phase-02-update-command-surfaces.md) | Completed | -| 3 | [Validate behavior](./phase-03-validate-behavior.md) | Completed | - -## Cross-Plan Dependencies - -None. No unfinished project plans found under `plans/`. - -## Key Files - -| File | Action | -|------|--------| -| `internal/modules/misc/misc.go` | Add command registration, parser/helper, handler | -| `internal/modules/misc/misc_test.go` | Update registration expectations | -| `internal/modules/misc/handlers_test.go` | Add handler behavior tests | -| `telegram-commands.json` | Add public Telegram command menu entry | -| `README.md` | Update misc module command list | - -## Acceptance Criteria - -- `/wheelofnames a, b, c` replies with exactly one trimmed item from `a`, `b`, `c`. -- `/wheelofnames` and `/wheelofnames , ,` reply with usage text. -- Empty comma segments do not appear as choices. -- Command is public and included in module registration, `/help`, and `telegram-commands.json`. -- Focused tests pass, then full `go test ./...` and `go vet ./...` pass. - -## Unresolved Questions - -None. diff --git a/plans/260703-0404-wheelofnames-misc-command/reports/pm-260703-0408-wheelofnames-complete.md b/plans/260703-0404-wheelofnames-misc-command/reports/pm-260703-0408-wheelofnames-complete.md deleted file mode 100644 index 3cfe052..0000000 --- a/plans/260703-0404-wheelofnames-misc-command/reports/pm-260703-0408-wheelofnames-complete.md +++ /dev/null @@ -1,34 +0,0 @@ -# Plan Complete: Wheel of Names Misc Command - -## Summary - -| Field | Value | -|---|---| -| Plan | `plans/260703-0404-wheelofnames-misc-command/plan.md` | -| Status | completed | -| Phases | 3/3 | -| Files changed | 5 implementation/docs files + plan files | -| Tests | `go test ./internal/modules/misc`, `go test ./cmd/server ./internal/modules/misc`, `go test ./...`, `go vet ./...` | - -## Work Completed - -- [x] Added public `/wheelofnames` command in `misc`. -- [x] Parses comma-separated options, trims whitespace, ignores empty entries. -- [x] Replies usage when no valid option exists. -- [x] Picks one valid option with casual randomness. -- [x] Updated README and `telegram-commands.json`. -- [x] Added focused handler and registration tests. - -## Documentation Updates - -- README module command list updated. -- No `docs/` update needed; no architecture, setup, security, or deploy behavior changed. - -## Known Limitations - -- Randomness is non-cryptographic by design. -- Duplicate options are preserved as weighting. - -## Unresolved Questions - -None. diff --git a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-01-api-client-and-env-config.md b/plans/260706-2220-wheelofnamesbeta-api-integration/phase-01-api-client-and-env-config.md deleted file mode 100644 index f62b0ec..0000000 --- a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-01-api-client-and-env-config.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -phase: 1 -title: API Client And Env Config -status: completed -priority: P2 -dependencies: [] ---- - -# Phase 1: API Client And Env Config - -## Overview - -Create a small misc-local HTTP client for the wheelofnames API and load its -endpoint/token from system env. This phase adds the remote render capability -without changing command behavior yet. - -## Requirements - -- Functional: Read `WHEELOFNAMES_API_URL` and `WHEELOFNAMES_API_TOKEN` from env. -- Functional: POST JSON to the configured full endpoint URL. -- Functional: Include Bearer auth only when token is non-empty. -- Functional: Return GIF bytes and metadata needed by the command. -- Non-functional: Use a bounded `http.Client` timeout, around 30 seconds. -- Non-functional: Never log or expose `WHEELOFNAMES_API_TOKEN`. -- Non-functional: Allow `http://` for private Coolify/Docker networks and - `https://` for public deployments; reject other schemes. - -## Architecture - -Add `wheelofnames_beta_api_client.go` in package `misc`. - -Suggested types: - -```go -type wheelBetaAPIClient struct { - HTTP *http.Client - URL string - Token string -} - -type wheelBetaAPIRequest struct { - Options []string `json:"options"` - WinnerIndex int `json:"winnerIndex"` - DurationMs int `json:"durationMs"` - HoldMs int `json:"holdMs"` - FPS int `json:"fps"` - Size int `json:"size"` - Theme string `json:"theme"` -} -``` - -Use a method like: - -```go -func (c *wheelBetaAPIClient) Render(ctx context.Context, options []string, winner int) ([]byte, error) -``` - -Return errors for: -- client not configured -- invalid URL scheme or malformed URL -- request build/transport failure -- non-2xx response -- non-`image/gif` content type -- empty or oversized body - -Keep body reads bounded. Use a conservative max such as 12 MiB because the bot -default remote render is now 512px, 20fps, and 7 seconds total. - -## Related Code Files - -- Create: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_api_client.go` -- Create: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_api_client_test.go` -- Modify: none in command path yet - -## Implementation Steps - -1. Add env constants: - - `WHEELOFNAMES_API_URL` - - `WHEELOFNAMES_API_TOKEN` -2. Add `newWheelBetaAPIClientFromEnv()` that trims env strings. -3. Implement URL validation and request construction. -4. Set request headers: - - `Content-Type: application/json` - - `Accept: image/gif` - - `Authorization: Bearer ` when token is non-empty -5. Encode request with fixed render options: - - `durationMs: 6000` - - `holdMs: 1000` - - `fps: 20` - - `size: 512` - - `theme: "classic"` -6. Read response body through `io.LimitReader`. -7. Add focused client tests using `httptest.Server`. - -## Success Criteria - -- [ ] URL unset returns a typed/configuration error that command can treat as fallback. -- [ ] Valid request test confirms JSON body and winner index. -- [ ] Valid request test confirms default render fields: - `durationMs=6000`, `holdMs=1000`, `fps=20`, `size=512`, `theme=classic`. -- [ ] Token test confirms Bearer auth header. -- [ ] HTTP 500 and 401 return errors without leaking response body as user text. -- [ ] Non-GIF content type returns error. -- [ ] Oversized response returns error. - -## Risk Assessment - -- Risk: Env URL points at service base URL instead of `/api/gif`. - Mitigation: Plan/docs explicitly define `WHEELOFNAMES_API_URL` as full endpoint. -- Risk: Remote service token appears in logs. - Mitigation: Never log request headers or token value; log only high-level status. -- Risk: 512px/20fps render takes longer than older 384px smoke path. - Mitigation: 30-second HTTP timeout and local fallback; tune only after - production timing data. diff --git a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-02-command-integration-and-fallback.md b/plans/260706-2220-wheelofnamesbeta-api-integration/phase-02-command-integration-and-fallback.md deleted file mode 100644 index 911d8f2..0000000 --- a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-02-command-integration-and-fallback.md +++ /dev/null @@ -1,105 +0,0 @@ ---- -phase: 2 -title: Command Integration And Fallback -status: completed -priority: P2 -dependencies: - - 1 ---- - -# Phase 2: Command Integration And Fallback - -## Overview - -Wire `/wheelofnamesbeta` to try the remote API first when configured, while -preserving the existing local GIF renderer as fallback and preserving Telegram -upload behavior. - -## Requirements - -- Functional: Keep `splitWheelOptions` and `pickWheelOption` as the only winner - selection source in the bot. -- Functional: Use remote GIF only when API call succeeds with valid GIF bytes. -- Functional: Fall back to `renderWheelOfNamesBetaGIF(options, winner)` on any - remote error. -- Functional: Preserve `sendAnimation`, `MessageThreadID`, filename, and - non-spoiler caption. -- Non-functional: Remote failure should log enough to diagnose status/error but - should not reply with stack traces or token-bearing details. -- Non-functional: Existing command behavior remains unchanged when env is unset. - -## Architecture - -Introduce a small orchestration helper in package `misc`: - -```go -func renderWheelOfNamesBetaAnimation(ctx context.Context, options []string, winner int) ([]byte, int, int, int, error) -``` - -Return bytes plus Telegram metadata (`duration`, `width`, `height`). Suggested -metadata: -- Remote success: duration `7`, width/height `512`. -- Local fallback: existing `wheelBetaDuration`, `wheelBetaSize`, - `wheelBetaSize`. - -Alternative: return a tiny struct: - -```go -type wheelBetaAnimation struct { - Data []byte - Duration int - Width int - Height int -} -``` - -The command handler should stay simple: -1. parse options -2. pick winner -3. call `renderWheelOfNamesBetaAnimation` -4. send animation -5. on final render/upload failure, text fallback remains `options[winner]` - -## Related Code Files - -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_command.go` -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta.go` only if helper placement requires it -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/handlers_test.go` - -## Implementation Steps - -1. Add the orchestration helper that creates the env client and attempts remote - render. -2. If client is unconfigured, skip remote without warning noise. -3. If client is configured but fails, log warning with safe fields: - - command: `wheelofnamesbeta` - - status/error category - - never token or request body -4. On remote success, return remote bytes and remote metadata. -5. On remote failure, call current local `renderWheelOfNamesBetaGIF`. -6. Update `wheelOfNamesBetaCommand` to use returned metadata in - `SendAnimationParams`. -7. Keep caption exactly `"Spinning..."`. - -## Success Criteria - -- [ ] URL unset path still sends local GIF and existing tests pass. -- [ ] URL configured path calls remote service and sends remote GIF. -- [ ] Remote success path uses Telegram metadata `duration=7`, - `width=512`, `height=512`. -- [ ] Remote failure path sends local GIF, not text, when local renderer works. -- [ ] Telegram upload failure still replies with the selected winner text. -- [ ] Caption does not contain the winner. -- [ ] Message thread forwarding remains covered. - -## Risk Assessment - -- Risk: Remote and local metadata diverge. - Mitigation: use explicit metadata struct; tests assert remote width/height and - local compatibility. -- Risk: Remote API chooses a different winner. - Mitigation: always pass `winnerIndex`; ignore remote winner headers for bot - caption/source of truth. -- Risk: A configured but down service makes the command slower. - Mitigation: timeout and fallback; consider lowering timeout later only after - production timing data. diff --git a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-03-deployment-docs-and-env-surfaces.md b/plans/260706-2220-wheelofnamesbeta-api-integration/phase-03-deployment-docs-and-env-surfaces.md deleted file mode 100644 index f9e2d46..0000000 --- a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-03-deployment-docs-and-env-surfaces.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -phase: 3 -title: Deployment Docs And Env Surfaces -status: completed -priority: P2 -dependencies: - - 1 - - 2 ---- - -# Phase 3: Deployment Docs And Env Surfaces - -## Overview - -Document the new optional environment variables across local and Coolify -deployment surfaces so the bot can find the wheelofnames service without code -changes. - -## Requirements - -- Functional: Document `WHEELOFNAMES_API_URL` as the full `/api/gif` endpoint. -- Functional: Document `WHEELOFNAMES_API_TOKEN` as the Bearer token matching the - wheelofnames service `API_TOKEN`. -- Non-functional: `.env.example` must use placeholders, not real secrets. -- Non-functional: Deploy docs must explain private network HTTP URL vs public - HTTPS URL options. -- Non-functional: Compose comments should not force the feature on by default - if the service is not running. - -## Architecture - -The bot remains a single long-polling service. The wheelofnames service is a -separate container or external service. The bot uses outbound HTTP only. -The current wheelofnames service `compose.yml` forwards its own env from the -shell with `${VAR:-default}` fallbacks, including `PORT`, `API_TOKEN`, -`MAX_CONCURRENT_RENDERS`, and render limits. - -Coolify/private-network example: - -```env -WHEELOFNAMES_API_URL=http://wheelofnames:3000/api/gif -WHEELOFNAMES_API_TOKEN= -``` - -Public URL example: - -```env -WHEELOFNAMES_API_URL=https://wheelofnames.example.com/api/gif -WHEELOFNAMES_API_TOKEN= -``` - -## Related Code Files - -- Modify: `/config/workspace/tiennm99/miti99bot/.env.example` -- Modify: `/config/workspace/tiennm99/miti99bot/compose.yml` -- Modify: `/config/workspace/tiennm99/miti99bot/docs/deploy-coolify-selfhosted.md` -- Optional modify: `/config/workspace/tiennm99/miti99bot/README.md` only if the - command description needs to mention remote rendering. - -## Implementation Steps - -1. Add optional env entries to `.env.example` under operational settings. -2. Add commented or placeholder entries in `compose.yml` near other operational - env vars. -3. Update deploy docs required/optional env table. -4. Add a short deployment note: - - URL unset = local renderer fallback. - - URL set + token = remote Remotion GIF rendering. - - Token should match wheelofnames service `API_TOKEN`. - - Bot default remote render request is 512px, 20fps, 7 seconds total. -5. Avoid suggesting multiple bot replicas; existing single-replica rule still - applies. - -## Success Criteria - -- [ ] `.env.example` includes both variables with safe placeholder values. -- [ ] `compose.yml` includes the env names without embedding secrets. -- [ ] Deploy docs tell operators how to wire both services. -- [ ] Docs state fallback behavior when env is absent or remote fails. - -## Risk Assessment - -- Risk: Docs imply a public bot ingress is required. - Mitigation: explicitly state this is outbound HTTP from the bot to - wheelofnames; Telegram transport stays long polling. -- Risk: Operators put a service base URL without `/api/gif`. - Mitigation: examples show full endpoint and implementation returns clear - safe error/fallback. diff --git a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-04-validation-and-regression-tests.md b/plans/260706-2220-wheelofnamesbeta-api-integration/phase-04-validation-and-regression-tests.md deleted file mode 100644 index 8c8562a..0000000 --- a/plans/260706-2220-wheelofnamesbeta-api-integration/phase-04-validation-and-regression-tests.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -phase: 4 -title: Validation And Regression Tests -status: completed -priority: P2 -dependencies: - - 1 - - 2 - - 3 ---- - -# Phase 4: Validation And Regression Tests - -## Overview - -Add focused regression tests and run the repo quality gates required for a -command integration touching HTTP, env config, and Telegram upload behavior. - -## Requirements - -- Functional: Test remote success, remote failure fallback, token header, and - malformed/non-GIF responses. -- Functional: Existing local renderer tests remain valid. -- Non-functional: Run lint/test gates required by `AGENTS.md`. -- Non-functional: Do not require a live wheelofnames service for unit tests. - -## Architecture - -Use `httptest.Server` for the wheelofnames API client. Keep tests in package -`misc` so they can use unexported helpers and existing test utilities. - -Test matrix: - -| Area | Scenario | Expected | -|---|---|---| -| Client | valid `200 image/gif` | returns GIF bytes | -| Client | default request payload | sends 6000ms spin, 1000ms hold, 20fps, 512px, classic | -| Client | token configured | sends `Authorization: Bearer ...` | -| Client | `401`/`500` | returns error | -| Client | `text/plain` 200 | returns error | -| Client | bad URL scheme | returns error | -| Command | env unset | local GIF path still sends animation | -| Command | remote success | upstream receives request; bot sends animation | -| Command | remote failure | bot sends local animation | -| Command | remote success in thread | message thread id preserved | - -## Related Code Files - -- Create: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_api_client_test.go` -- Modify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/handlers_test.go` -- Verify: `/config/workspace/tiennm99/miti99bot/internal/modules/misc/misc_test.go` - -## Implementation Steps - -1. Write client tests before wiring command behavior where practical. -2. Add command tests with `t.Setenv` and `httptest.Server`. -3. Confirm tests do not depend on real network. -4. Run focused package tests: - ```sh - go test ./internal/modules/misc - ``` -5. Run full gates: - ```sh - go test ./... - go vet ./... - ``` -6. If docs changed only, no Telegram command registration update is needed. - -## Success Criteria - -- [ ] `go test ./internal/modules/misc` passes. -- [ ] `go test ./...` passes. -- [ ] `go vet ./...` passes. -- [ ] Tests prove winner index is sent to remote API. -- [ ] Tests prove default render config is sent to remote API. -- [ ] Tests prove fallback keeps user-visible behavior stable. - -## Risk Assessment - -- Risk: Tests become flaky due package-level env. - Mitigation: use `t.Setenv`, avoid `t.Parallel` in env-sensitive tests. -- Risk: Recording bot cannot inspect uploaded file bytes. - Mitigation: assert upstream request count/body plus `sendAnimation` metadata; - client tests validate returned bytes. diff --git a/plans/260706-2220-wheelofnamesbeta-api-integration/plan.md b/plans/260706-2220-wheelofnamesbeta-api-integration/plan.md deleted file mode 100644 index c5199ff..0000000 --- a/plans/260706-2220-wheelofnamesbeta-api-integration/plan.md +++ /dev/null @@ -1,161 +0,0 @@ ---- -title: Wheelofnamesbeta API Integration -description: >- - Route /wheelofnamesbeta GIF rendering through the self-hosted wheelofnames API - when configured, with the current local renderer as fallback. -status: completed -priority: P2 -branch: main -tags: - - feature - - backend - - api -blockedBy: [] -blocks: [] -created: '2026-07-06T15:20:57.412Z' -createdBy: 'ck:plan' -source: skill ---- - -# Wheelofnamesbeta API Integration - -## Overview - -Integrate the existing Go `/wheelofnamesbeta` command with the new -`wheelofnames` Remotion service by reading the API endpoint from system env. -When `WHEELOFNAMES_API_URL` is set, the command sends the parsed options and -locally selected `winnerIndex` to that endpoint and uploads the returned GIF to -Telegram. When the env is unset, invalid, timed out, unauthorized, or the -service returns non-GIF/non-2xx, the bot keeps using the current local GIF -renderer. - -Checked service repo state on 2026-07-06: -- `/config/workspaces/tiennm99/wheelofnames` is clean. -- Latest commit: `4cf72ea fix: align wheel labels and compose env`. -- Service now has dedicated radial label layout logic in - `src/remotion/wheel-label-layout.js`, tests for label layout, and - `compose.yml` env defaults via `${VAR:-default}`. -- API schema still accepts `durationMs`, `holdMs`, `fps`, `size`, and `theme`. -- Service composition duration is `durationMs + holdMs`; the requested bot - payload below renders a 7-second GIF. - -Scope Challenge: -- Existing code: `/wheelofnamesbeta` already parses options, picks the winner, - renders local GIF bytes, sends `sendAnimation`, preserves message threads, - and falls back to text on local render/upload failure. -- Minimum changes: add a small API client, wire command render path through - remote-or-local selection, add env docs, and test remote success/failure. -- Complexity: expected 5-7 touched files, one new client file, four focused - phases. No command rename, stats migration, persistent jobs, or async flow. -- Selected mode: SCOPE REDUCTION / fast plan. The API service already exists; - this plan only integrates the command. - -## Architecture - -```text -/wheelofnamesbeta message - | - | splitWheelOptions + pickWheelOption (existing) - v -renderWheelOfNamesBetaAnimation(ctx, options, winner) - | - | if WHEELOFNAMES_API_URL unset - | -> existing renderWheelOfNamesBetaGIF fallback - | - | POST WHEELOFNAMES_API_URL - | Authorization: Bearer $WHEELOFNAMES_API_TOKEN when set - | JSON: { options, winnerIndex, durationMs, holdMs, fps, size, theme } - v -remote GIF bytes -> Telegram sendAnimation - | - | on any remote error - v -existing local GIF renderer -> Telegram sendAnimation -``` - -Environment contract: -- `WHEELOFNAMES_API_URL`: optional full endpoint URL, expected to include - `/api/gif`, for example `http://wheelofnames:3000/api/gif`. -- `WHEELOFNAMES_API_TOKEN`: optional Bearer token. Required for production - `wheelofnames` service deployments because that service refuses production - startup without `API_TOKEN`. - -Remote render request values should be fixed in code for now: - -```json -{ - "options": ["alice", "bob", "carol"], - "winnerIndex": 1, - "durationMs": 6000, - "holdMs": 1000, - "fps": 20, - "size": 512, - "theme": "classic" -} -``` - -Do not add user-facing flags, persistent storage, or dynamic theme selection in -this integration pass. - -## Phases - -| Phase | Name | Status | -|-------|------|--------| -| 1 | [API Client And Env Config](./phase-01-api-client-and-env-config.md) | Completed | -| 2 | [Command Integration And Fallback](./phase-02-command-integration-and-fallback.md) | Completed | -| 3 | [Deployment Docs And Env Surfaces](./phase-03-deployment-docs-and-env-surfaces.md) | Completed | -| 4 | [Validation And Regression Tests](./phase-04-validation-and-regression-tests.md) | Completed | - -## Dependencies - -## Cross-Plan Dependencies - -| Relationship | Plan | Status | -|---|---|---| -| Uses output from | `plans/260706-1441-wheelofnames-remotion-api/plan.md` | completed | - -No unfinished project plans overlap this scope. - -## Key Files - -| File | Action | -|---|---| -| `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_api_client.go` | Create remote API client and env loader | -| `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_command.go` | Route command through remote-or-local renderer | -| `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta.go` | Keep local renderer unchanged as fallback | -| `/config/workspace/tiennm99/miti99bot/internal/modules/misc/handlers_test.go` | Add command-level remote success/fallback tests | -| `/config/workspace/tiennm99/miti99bot/internal/modules/misc/wheelofnames_beta_api_client_test.go` | Create focused client tests | -| `/config/workspace/tiennm99/miti99bot/.env.example` | Document optional API env vars | -| `/config/workspace/tiennm99/miti99bot/compose.yml` | Add commented Coolify/private-network env hints | -| `/config/workspace/tiennm99/miti99bot/docs/deploy-coolify-selfhosted.md` | Document deployment wiring with wheelofnames service | - -## Acceptance Criteria - -- With `WHEELOFNAMES_API_URL` unset, `/wheelofnamesbeta` behaves like today. -- With `WHEELOFNAMES_API_URL` set and remote returns `200 image/gif`, the bot - uploads those GIF bytes via `sendAnimation`. -- The remote request uses `durationMs=6000`, `holdMs=1000`, `fps=20`, - `size=512`, and `theme=classic` by default. -- The request body contains the original parsed options and the locally chosen - `winnerIndex`; the service must not be the source of truth for the winner. -- If `WHEELOFNAMES_API_TOKEN` is set, request includes - `Authorization: Bearer ` and tests verify it. -- Remote failures fall back to the existing local GIF renderer and do not reveal - the winner in the caption. -- Timeout is bounded with an HTTP client timeout; no goroutine can hang on a - wedged render service. -- Docs and env examples explain that URL is optional and token is required for - production wheelofnames service auth. -- Focused tests pass, then `go test ./...` and `go vet ./...` pass. - -## Out Of Scope - -- Replacing `/wheelofnames`; only `/wheelofnamesbeta` changes. -- Removing the local Go GIF renderer. -- Async job polling, object storage, or cached GIF URLs. -- User-selectable themes, sizes, fps, or command syntax changes. -- Stats migrations or Telegram command menu changes. - -## Unresolved Questions - -None. diff --git a/plans/reports/260701-1000-mongodb-stats-design-research.md b/plans/reports/260701-1000-mongodb-stats-design-research.md deleted file mode 100644 index e98030f..0000000 --- a/plans/reports/260701-1000-mongodb-stats-design-research.md +++ /dev/null @@ -1,265 +0,0 @@ ---- -type: research -topic: mongodb-stats-design -created_at: 2026-07-01T03:00:06Z ---- - -# Research Report: MongoDB Stats Design - -## Executive Summary - -For this bot's `/stats` feature, best primary model is one aggregate document per -`(command, user)` pair, plus one anonymous command bucket when sender has no -username. This keeps documents bounded, uses MongoDB atomic `$inc`, and makes all -current views queryable without dynamic object keys. - -Do not store stats as "one user document with commands map" or "one command -document with users map" as primary shape. Those designs make one query easy and -the opposite query expensive, create hot growing documents, and push us toward -dynamic field names. Pair docs are boring and better. - -Recommended document: - -```js -{ _id: "ping:42", cmd: "ping", uid: 42, user: "alice", n: 3 } -{ _id: "ping", cmd: "ping", n: 2 } // no public username -``` - -## Methodology - -- Sources consulted: MongoDB official docs, Go driver docs, local codebase. -- Date: 2026-07-01. -- Key terms: MongoDB schema design, unbounded arrays, `$inc`, atomicity, - aggregation `$group`, `$sort`, compound indexes, ESR guideline. -- Scope: stats module only. Bot persistence remains one collection per module. - -## Codebase Context - -- Repo: Go Telegram bot. -- Storage: `internal/storage` has one MongoDB collection per module. -- Current stats: `count:*`, `user:*`, `pair:*` key families in one collection. -- Current views: - - `/stats`: top commands. - - `/stats users`: top users. - - `/stats user `: commands by user. - - `/stats cmd `: users by command. -- Mongo docs already store flattened BSON fields, so stats fields can be queried. - -## Key Findings - -### 1. Avoid Growing Nested User/Command Maps - -MongoDB docs warn against unbounded arrays because growing documents hurt -performance and risk document size limits. The same practical concern applies to -ever-growing embedded maps like `commands` under a user or `users` under a -command: one document grows forever and becomes a write hotspot. - -Source: MongoDB "Avoid Unbounded Arrays": -https://www.mongodb.com/docs/manual/data-modeling/design-antipatterns/unbounded-arrays/ - -### 2. Use Atomic `$inc` For Counts - -MongoDB `$inc` creates missing numeric fields, increments by the given amount, -and is atomic within one document. This fits per-pair counter documents exactly. - -Source: MongoDB `$inc` operator: -https://www.mongodb.com/docs/manual/reference/operator/update/inc/ - -MongoDB write operations are atomic at single-document level. Multi-document -updates are not atomic as a whole, so each counter increment should update one -aggregate document. - -Source: MongoDB atomicity: -https://www.mongodb.com/docs/manual/core/write-operations-atomicity/ - -### 3. Use Aggregation For Cross-Dimension Totals - -MongoDB `$group` combines documents by a group key. That fits: - -```js -// top commands -[{ $group: { _id: "$cmd", n: { $sum: "$n" } } }] - -// top users -[{ $match: { uid: { $gt: 0 } } }, - { $group: { _id: "$uid", user: { $first: "$user" }, n: { $sum: "$n" } } }] -``` - -Source: MongoDB `$group`: -https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/ - -Use `$sort` then `$limit` for top K. MongoDB can coalesce adjacent sort/limit so -only top N must be kept in memory. - -Source: MongoDB `$sort` optimization: -https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/ - -### 4. Index From Query Shape, Not Guesswork - -MongoDB aggregation can use indexes, especially early `$match` and `$sort` -stages. The ESR guideline says equality fields first, then sort, then range for -compound indexes. - -Sources: -- Aggregation pipeline optimization: - https://www.mongodb.com/docs/manual/core/aggregation-pipeline-optimization/ -- ESR guideline: - https://www.mongodb.com/docs/manual/tutorial/equality-sort-range-guideline/ -- Indexing strategies: - https://www.mongodb.com/docs/manual/applications/indexes/ - -For this module, start with no extra indexes or a tiny set. Traffic likely low. -Add indexes once data grows or `/stats` gets slow. - -Useful indexes if needed: - -```js -db.stats.createIndex({ cmd: 1, n: -1 }) // /stats cmd -db.stats.createIndex({ uid: 1, n: -1 }) // /stats user -db.stats.createIndex({ user: 1, updatedAt: -1 }) // resolve username -``` - -Do not add many indexes early. Each index costs memory/disk and write work. - -## Comparative Analysis - -| Shape | Example | Good | Bad | Verdict | -|---|---|---|---|---| -| User-rooted | `{ uid, user, commands: { ping: 3 } }` | `/stats user` easy | `/stats cmd` scans users, dynamic keys, growing doc | Reject | -| Command-rooted | `{ cmd, users: { "42": { n: 3 } } }` | `/stats cmd` easy | `/stats user` scans commands, hot command docs, dynamic keys | Reject | -| Pair-rooted | `{ cmd, uid, user, n }` | All 4 views queryable, bounded docs, atomic `$inc` | Aggregation needed for totals | Recommend | -| Event log | `{ cmd, uid, user, at }` per invocation | Full history | Too much data, needs rollups | Overkill | - -## Implementation Recommendations - -### Primary Model - -Use one document per aggregate: - -```go -type usageEntry struct { - Cmd string `bson:"cmd" json:"cmd"` - UserID int64 `bson:"uid,omitempty" json:"uid,omitempty"` - Username string `bson:"user,omitempty" json:"user,omitempty"` - N int64 `bson:"n" json:"n"` -} -``` - -Keys: - -```text - anonymous/no-public-username bucket -: named user pair bucket -``` - -### Write Path - -Use MongoDB `UpdateOne` with `upsert: true`: - -```js -db.stats.updateOne( - { _id: "ping:42" }, - { - $set: { cmd: "ping", uid: 42, user: "alice" }, - $inc: { n: 1 }, - $currentDate: { updatedAt: true } - }, - { upsert: true } -) -``` - -This avoids read-modify-write races in the current generic `DocStore` loop. - -### Read Path - -Use Mongo queries/aggregation when backend is MongoDB. Keep in-memory fallback -for tests and no-database local run. - -Queries: - -```js -// /stats -db.stats.aggregate([ - { $group: { _id: "$cmd", n: { $sum: "$n" } } }, - { $sort: { n: -1, _id: 1 } }, - { $limit: 20 } -]) - -// /stats users -db.stats.aggregate([ - { $match: { uid: { $gt: 0 }, user: { $type: "string", $ne: "" } } }, - { $group: { _id: "$uid", user: { $first: "$user" }, n: { $sum: "$n" } } }, - { $sort: { n: -1, user: 1 } }, - { $limit: 20 } -]) - -// /stats user alice -db.stats.find({ uid: 42 }).sort({ n: -1, cmd: 1 }).limit(20) - -// /stats cmd ping -db.stats.find({ cmd: "ping", uid: { $gt: 0 } }).sort({ n: -1, user: 1 }).limit(20) -``` - -### Username Rename - -When a user calls a command with a new username, refresh all docs for that `uid` -with `UpdateMany({ uid }, { $set: { user } })`. It is not atomic as a whole, but -stats are best-effort and eventual consistency is acceptable. - -### Migration - -Existing prod data likely in old key prefixes. Options: - -1. No migration: simplest, stats reset after deploy. -2. Read old + new for one release: more code, little value. -3. One-off migration script: only if historical stats matter. - -Recommendation: no migration unless user explicitly wants historical stats. - -## Common Pitfalls - -- Do not use dynamic fields like `commands.ping` or `users.42` as primary model. -- Do not grow one document forever. -- Do not use generic get-then-put for hot counters on MongoDB. -- Do not over-index small collections. -- Do not use username as identity. Telegram username can change; use `uid`. - -## Decision - -Recommended: pair-rooted aggregate docs, Mongo atomic upsert increments, Mongo -aggregation for display views, memory fallback for tests. - -## References - -- MongoDB Avoid Unbounded Arrays: - https://www.mongodb.com/docs/manual/data-modeling/design-antipatterns/unbounded-arrays/ -- MongoDB `$inc`: - https://www.mongodb.com/docs/manual/reference/operator/update/inc/ -- MongoDB Atomicity: - https://www.mongodb.com/docs/manual/core/write-operations-atomicity/ -- MongoDB `$group`: - https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/ -- MongoDB `$sort`: - https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/ -- MongoDB Aggregation Optimization: - https://www.mongodb.com/docs/manual/core/aggregation-pipeline-optimization/ -- MongoDB ESR Guideline: - https://www.mongodb.com/docs/manual/tutorial/equality-sort-range-guideline/ -- MongoDB Indexing Strategies: - https://www.mongodb.com/docs/manual/applications/indexes/ -- MongoDB Go Driver Update: - https://www.mongodb.com/docs/drivers/go/current/crud/update/ -- MongoDB Go Driver Indexes: - https://www.mongodb.com/docs/drivers/go/current/indexes/ - -## Next Steps - -1. Confirm pair-rooted aggregate docs. -2. Finish stats implementation. -3. Run `go test ./internal/modules/stats ./internal/storage`. -4. Optional later: add Mongo integration test if local Mongo available. - -## Unresolved Questions - -- Keep old stats by migration, or accept reset? -- Add indexes now, or wait until data size/latency proves need? diff --git a/plans/reports/260706-1134-wheelofnamesbeta-gif-research.md b/plans/reports/260706-1134-wheelofnamesbeta-gif-research.md deleted file mode 100644 index 8ce3beb..0000000 --- a/plans/reports/260706-1134-wheelofnamesbeta-gif-research.md +++ /dev/null @@ -1,151 +0,0 @@ ---- -type: research -date: 2026-07-06T11:34:00Z -topic: wheelofnamesbeta gif generation and Telegram delivery -skills: - - ck:research - - ck:project-organization ---- - -# Research Report: Wheelofnamesbeta Gif Generation - -## Executive Summary - -Use Go stdlib `image/gif` for GIF encoding. It supports multi-frame GIFs via -`EncodeAll`, frame delay in 1/100s, and loop control. Add only -`golang.org/x/image/font/basicfont` for self-contained bitmap text if labels or -winner text must appear inside frames. Avoid ffmpeg/cgo/runtime binaries. - -Send the generated GIF with Telegram `sendAnimation`, not `sendPhoto`. -Telegram Bot API defines `sendAnimation` for GIF or silent H.264/MPEG-4 -animation files, max 50 MB at current docs. Generate in memory, upload with -`models.InputFileUpload`, and set caption to the winner. - -Recommended `/wheelofnamesbeta` behavior: parse same comma input as -`/wheelofnames`, choose winner once, render 6s spin plus 1s hold. Keep canvas -small, frame count low, and palette fixed to avoid CPU/file-size spikes. - -## Research Methodology - -- Sources consulted: 4 -- Date: 2026-07-06 -- Gemini: disabled by `.ck.json`; used WebSearch -- Key terms: Go animated GIF EncodeAll, golang.org/x/image font basicfont, - Telegram Bot API sendAnimation, GIF optimization frame count palette - -## Key Findings - -### 1. Technology Overview - -`image/gif` is in Go stdlib and implements GIF decoding/encoding. `EncodeAll` -writes multiple paletted images as one GIF. The `GIF` struct stores successive -frames, delays in 1/100s, and loop count. This maps directly to "6s spin + 1s -hold": e.g. 30 frames at 20/100s each, then 1 final frame at 100/100s. - -`golang.org/x/image/font` provides text drawing interfaces. `basicfont` provides -fixed-size font faces. `Face7x13` is self-contained, uses printable ASCII, and -needs no font files. - -Telegram Bot API `sendAnimation` sends GIF or silent H.264/MPEG-4 animation -files and returns a sent message. Current limit: 50 MB. It supports normal -`chat_id` and `message_thread_id`, so it works for groups/topics unlike -`sendMessageDraft`. - -### 2. Current State And Trends - -- GIF is simple but inefficient compared to MP4. For this bot, low-res generated - vector-ish frames are small enough. -- Go stdlib avoids external binary dependencies and is deploy-safe on Coolify. -- `x/image/basicfont` is enough for readable short text; no custom TTF asset. - -### 3. Best Practices - -- Render fixed small canvas, e.g. 320x320. -- Use fixed palette and `image.NewPaletted`. -- Keep frame count low: 30 spin frames + 1 hold frame. -- Delay: spin frames 20/100s = 6s total; final hold 100/100s = 1s. -- Set `LoopCount: -1` so the GIF plays once. Telegram clients may still expose - replay behavior, but file intent is one pass. -- Put exact winner in caption too, because GIF autoplay/download can vary by - client settings. - -### 4. Security Considerations - -- No user-provided image decoding; generated pixels only, so image bomb risk low. -- Bound input names and rendered text length to avoid huge memory/caption work. -- Never write temp files; generate in memory. -- Avoid shelling to ffmpeg from command handler. - -### 5. Performance Insights - -GIF frame memory is width * height * frame count. At 320x320 and 31 frames, raw -paletted data is about 3.2 MB before encoding plus overhead. Acceptable for one -long-polling handler. - -Drawing simple wedges/lines/text in Go is CPU-cheap. Avoid antialiasing and -large canvases. 20/100s frame delay keeps total frames modest. - -## Comparative Analysis - -| Option | Pros | Cons | Verdict | -|---|---|---|---| -| stdlib `image/gif` | No new heavy dependency, pure Go, direct timing control | Basic drawing only | Use | -| `x/image/font/basicfont` | Small official Go subrepo, no font files | ASCII-ish visual, small font | Use for labels | -| `gg` | Easier antialiased drawing/text | Adds third-party dependency | Reject for beta | -| ffmpeg MP4/GIF | Better compression/quality | External binary, shell/runtime complexity | Reject | - -## Implementation Recommendations - -### Quick Start Guide - -1. Add `/wheelofnamesbeta` command in `misc`. -2. Reuse `splitWheelOptions` and `pickWheelOption`. -3. Generate GIF bytes in memory. -4. Send using `b.SendAnimation` with `models.InputFileUpload`. -5. Caption final winner. -6. Add tests that assert `sendAnimation` and encoded GIF timing. - -### Code Sketch - -```go -buf := bytes.Buffer{} -err := gif.EncodeAll(&buf, &gif.GIF{ - Image: frames, - Delay: delays, // 30 x 20, then 100 - LoopCount: -1, -}) -_, err = b.SendAnimation(ctx, &bot.SendAnimationParams{ - ChatID: msg.Chat.ID, - MessageThreadID: msg.MessageThreadID, - Animation: &models.InputFileUpload{ - Filename: "wheelofnamesbeta.gif", - Data: bytes.NewReader(buf.Bytes()), - }, - Duration: 7, - Caption: "Winner: " + winner, -}) -``` - -### Common Pitfalls - -- `sendPhoto` can flatten GIF to a static image. Use `sendAnimation`. -- GIF delays are centiseconds, not milliseconds. -- Large canvases or many frames can bloat files fast. -- Telegram client autoplay is user-controlled; caption must contain winner. - -## Resources And References - -- Go `image/gif` docs: https://pkg.go.dev/image/gif -- Go `golang.org/x/image/font` docs: https://pkg.go.dev/golang.org/x/image/font -- Go `basicfont` docs: https://pkg.go.dev/golang.org/x/image/font/basicfont -- Telegram `sendAnimation`: https://core.telegram.org/bots/api#sendanimation - -## Next Steps - -1. Implement pure-Go GIF renderer in `internal/modules/misc`. -2. Add `/wheelofnamesbeta` command surfaces and tests. -3. Run focused misc tests, full `go test ./...`, and `go vet ./...`. - -## Unresolved Questions - -- None.