chore: switch to Apache-2.0 license; import plans from upstream miti99bot

This commit is contained in:
tiennm99 committed 2026-05-08 22:48:34 +07:00
1 parent ab617ade63
commit 5d27cd8356
57 files changed
+9724 -17

No files matched your search

@@ -0,0 +1,173 @@
# Phase 02 — Emoji module (`loldle-emoji`)
## Context
- [Research: overview + emoji](../reports/researcher-260424-2215-loldle-emoji-and-modes-overview.md)
- Template: `src/modules/loldle/` (classic)
- Dependency: phase 01 (`emojis.json` written by scraper, `normalize` helper).
## Overview
**Priority:** P1 (ship first — simplest mode).
**Status:** pending.
Guess the champion from an emoji clue. All text-rendered — Telegram renders
emojis natively. No images, no audio, no DDragon.
## Key insights
- Emoji sequences are handcrafted by loldle.net (not algorithmic). We only
have what they ship — can't compute our own.
- Progressive reveal on loldle.net works by "unlock one emoji per wrong
guess". On Telegram, we keep it simpler: **show all emojis upfront, fewer
guesses**. Saves edit-message roundtrips.
- Reuse classic's `stats:<subject>` shape verbatim.
## Requirements
**Functional**
- `/loldle_emoji` → show current round or start fresh; submit a guess if
`<champion>` arg provided.
- `/loldle_emoji_giveup` → reveal answer, record loss.
- `/loldle_emoji_stats` → show per-subject play stats.
- 5 guesses per round.
- Subject = user (DM) or chat (group), same rule as classic.
- Champion name lookup identical to classic (case/space/punctuation-
insensitive, unique-prefix fallback).
**Non-functional**
- Pure KV storage (no D1). Auto-prefixed key `loldle-emoji:game:<subject>`.
- Round state: `{ target, guesses, startedAt }` — same shape as classic.
- Champion pool comes from `emojis.json` (phase 01). Only champions with a
non-empty emoji string are eligible.
## Architecture
```
src/modules/loldle-emoji/
├── index.js # { name, commands, init } export
├── handlers.js # handleEmoji, handleGiveup, handleStats
├── state.js # loadGame/saveGame/clearGame/loadStats/recordResult
├── lookup.js # findChampion over emojis.json (thin wrapper, uses util/normalize-name.js)
├── render.js # board render: emoji block + guesses list
├── emojis.json # [{ championName, emojis:"🦊✨💫" }, ...] (generated)
└── README.md # usage + data source notes
```
## Related code files
**Modify**
- `src/modules/index.js` — add `"loldle-emoji"` to static import map.
- `wrangler.toml` `[vars].MODULES` — append `,loldle-emoji`.
- `.env.deploy` (local) — append `,loldle-emoji` to MODULES.
**Create**
- Six files listed in Architecture above.
**Delete:** none.
## Implementation steps
1. **Copy `src/modules/loldle/state.js`** → `src/modules/loldle-emoji/state.js`.
Change `MAX_GUESSES = 5`. No other changes needed; same KV shape.
2. **Create `lookup.js`**:
```js
import { normalize } from "../../util/normalize-name.js";
export function findChampion(pool, input) {
const q = normalize(input);
if (!q) return null;
const exact = pool.find((c) => normalize(c.championName) === q);
if (exact) return exact;
const prefix = pool.filter((c) =>
normalize(c.championName).startsWith(q));
return prefix.length === 1 ? prefix[0] : null;
}
```
3. **Create `render.js`** — render the current board:
```
🎭 <emoji sequence>
Guesses (<n>/<MAX>):
• <name1> ❌
• <name2> ❌
```
HTML-escape every champion name via `src/util/escape-html.js`.
4. **Create `handlers.js`** modelled on `loldle/handlers.js`:
- Same subject resolution (`getSubject`).
- Same arg parsing.
- `pickRandomChampion()` picks from `emojisData` (skip champions with
empty emoji string, if any).
- Win / loss / giveup messages reuse classic's tone; swap "classic" →
"emoji" in copy. No stickers for v1 (YAGNI — can add later).
- On win: "🎉 Got it! <champ> — solved in Nx/5" + stats update + KV clear.
- On loss: "❌ Answer was <champ>" + stats update + KV clear.
5. **Create `index.js`**:
```js
import { handleGiveup, handleEmoji, handleStats } from "./handlers.js";
let db = null;
export default {
name: "loldle-emoji",
init: async ({ db: store }) => { db = store; },
commands: [
{ name: "loldle_emoji", visibility: "public",
description: "Emoji loldle — guess the champion from emojis",
handler: (ctx) => handleEmoji(ctx, db) },
{ name: "loldle_emoji_giveup", visibility: "public",
description: "Reveal the current emoji answer",
handler: (ctx) => handleGiveup(ctx, db) },
{ name: "loldle_emoji_stats", visibility: "public",
description: "Show your emoji stats (wins, streak)",
handler: (ctx) => handleStats(ctx, db) },
],
};
```
6. **Register** — add to `src/modules/index.js` import map, MODULES env.
7. **Write minimal README.md** — commands table, KV prefix, data source
note ("regenerated by `npm run scrape:loldle-data`").
8. **Run `npm run dev`**, point a test bot at it, verify:
- `/loldle_emoji` shows emojis + empty board.
- `/loldle_emoji Ahri` (assuming Ahri is the answer) wins.
- `/loldle_emoji_giveup` reveals answer.
- `/loldle_emoji_stats` reports play counts.
## Todo
- [ ] Create folder + 6 files per Architecture
- [ ] Copy state.js from classic, lower MAX_GUESSES to 5
- [ ] Import normalize helper from util
- [ ] Render board with HTML-escape
- [ ] Handlers ported from classic, tone tweaked
- [ ] Register in `src/modules/index.js` + MODULES env
- [ ] Write README
- [ ] Local smoke-test (`npm run dev` + test bot)
## Success criteria
- Module loads at `installDispatcher` without conflicts.
- `/loldle_emoji` runs end-to-end against loldle.net data.
- Stats persist across rounds, isolated from classic loldle.
## Risks
| Risk | Mitigation |
|------|-----------|
| `emojis.json` missing some champions | Pool is whatever loldle.net provides; guard null at render time |
| Emoji rendering differs across Telegram clients | Use standard Unicode emojis (loldle.net already does); no skin-tone variants |
| Player confusion vs classic (two loldle-like commands) | Distinct command name + distinct welcome copy |
## Security
- Input passes through `normalize` (strips non-alphanum) before comparison.
- HTML escape all user-submitted and champion-name text in reply.
## Next steps
Phase 06 covers tests. Phase 03 (quote) can be built in parallel — same
template.