A Discord bot that runs Esm Famil (a quick, chaotic category-and-letter word game) plus community-managed word lists with weighted role voting.
- Opens a join lobby with Join/Leave buttons (and
!ef join/!ef leave). - Picks a random letter each game.
- Runs through a set of categories (names, animals, countries, etc.).
- Gives players think time (messages typed too early get deleted if possible), then a short SEND window.
- Scores answers:
- If multiple players submit the same valid word, points are split evenly.
- If no one answers correctly, the bot suggests an example (when available).
Members can propose additions/deletions to the bot’s word lists. Votes are weighted by role, and proposals expire after 5 minutes if they don’t hit the threshold.
Supported categories:
word(English dictionary list fromwords.json)animal,bodypart,city,color,country,fruit,job(CSV-backed lists)
- Python 3.9+ (recommended: 3.11)
- A Discord application + bot token
- Dependencies:
discord.py>=2.3.0,<3python-dotenv>=1.0.0
Install:
pip install -r requirements.txt- Clone the repo:
git clone <your-repo-url>
cd esm-famil- Create your
.env:
cp .env.example .env- Fill in at minimum:
DISCORD_BOT_TOKEN="...your token..."
-
Prepare data files (see Data files below).
-
Run the bot:
python bot.pyThis bot requires these privileged intents:
- Message Content Intent (because it reads messages for answers)
- Server Members Intent (because it reads roles for vote weights)
In the Discord Developer Portal:
- Open your bot application
- Go to Bot settings
- Enable:
- MESSAGE CONTENT INTENT
- SERVER MEMBERS INTENT
If you skip this, the bot will run and then quietly fail in the most infuriating ways.
See .env.example for all options.
DISCORD_BOT_TOKEN
Your bot token.
COMMAND_PREFIX(default:!)
ESM_FAMIL_CHANNEL_ID- If set, Esm Famil commands only work in that channel.
- If unset, commands work anywhere.
BOMB_PARTY_CHANNEL_ID- If set, word list voting commands for
wordare also allowed there.
- If set, word list voting commands for
DATA_DIR(default:data) Folder containing your CSV/JSON word data.WORDS_DICTIONARY_FILE(default:DATA_DIR/words.json) Override if you move/rename the dictionary file.
JOIN_WINDOW_SECONDS(default: 30)ROUNDS(default: 9)TIME_TO_ANSWER(default: 8)SEND_WINDOW_SECONDS(default: 3.5)TIME_BETWEEN_ROUNDS(default: 3)POINTS_PER_WORD(default: 100)MIN_PLAYERS(default: 2)
MEMBER_ROLE_ID(optional) If set, this role gets voting weight 1.ROLE_WEIGHTS(optional) Manual overrides in the format:ROLE_WEIGHTS="123=2,456=5"ADD_WORD_THRESHOLD(default: 10)DELETE_WORD_THRESHOLD(default: 15)
The bot also includes a built-in “ladder” of roles with auto-assigned weights (see config/constants.py).
The bot validates answers using:
- Category CSV files (for Animals, Countries, etc.)
- A dictionary JSON (
words.json) for “English Word”
By default, files are expected in DATA_DIR (default: ./data), with these names:
| Category | Filename |
|---|---|
| Animals | animals_by_letter.csv |
| Body Parts | body_parts_by_letter.csv |
| Cities | cities_by_letter.csv |
| Colors | colors.csv |
| Countries | countries_by_letter.csv |
| Fruits & Vegetables | fruits_vegetables_by_letter.csv |
| Jobs | jobs_by_letter.csv |
| English dictionary | words.json |
There are two behaviors in the code:
-
Validation loader (
load_category_map)
It reads the file as plain text and splits on commas and newlines.
Example acceptable formats:- Comma-separated:
Apple, Apricot, Avocado Banana, Blueberry - One-per-line:
Apple Apricot Avocado
- Comma-separated:
-
Community “add/delete” editor (CSV grid) The community add/delete commands treat the CSV as a grid with 26 columns (A–Z), and multiple rows.
- Each word is stored in the column matching its first letter.
- If the column is full, a new row is appended.
- Words are checked case-insensitively.
Practical recommendation:
If you want community add/delete to work cleanly, store category files as a 26-column grid (A–Z) with as many rows as you need.
Example (first row only, shown abbreviated):
Ant,Bear,Cat,,,,,,,,,,,,,,,,,,,,,,,,If you start empty, you can create a single blank row with 26 empty cells:
,,,,,,,,,,,,,,,,,,,,,,,,,words.json can be:
- A list:
["apple", "banana", "zebra"]
- Or a dict (keys are used as words):
{"apple": true, "banana": true}
All words are treated case-insensitively.
All game commands are under !esm_famil with alias !ef.
!ef
Shows commands and categories.
!ef start
Opens the lobby and runs the game.!ef join
Join the lobby (same as clicking Join).!ef leave
Leave the lobby (same as clicking Leave).
!ef add <category> <word1, word2, ...>- Example:
!ef add animal Kangaroo, Tiger
- Example:
!ef check <category> <word>- Example:
!ef check country Canada
- Example:
!ef delete <category> <word>- Example:
!ef delete animal Tiger
- Example:
Valid categories:
word, animal, bodypart, city, color, country, fruit, job
Channel rules:
wordactions are allowed in Esm Famil channel and optionally Bomb Party channel.- Other categories are restricted to Esm Famil channel when
ESM_FAMIL_CHANNEL_IDis set.
A submission is considered valid if:
- It starts with the game letter
- It passes category checks:
- Names: always accepted (wild west rules)
- English Word: must exist in
words.json(with light plural/singular normalization) - Other categories: must match category data for the letter (with some partial matching)
- Jobs: has extra logic to match sub-phrases inside multi-word job titles
The bot also blocks single-letter answers except:
- Names category
- English Word category with
aori
MIT (see LICENSE).