A Discord bot that runs lightweight, self-paced English quizzes in your server: Picture, Word, Grammar, and Idiom cards. Each quiz advances only after N unique users answer correctly, so the channel doesn’t turn into an endless spam waterfall (Discord already has enough of those).
- Posts quiz cards on a loop for each quiz type (pic / word / grammar / idiom).
- Users answer by clicking buttons (multiple attempts allowed).
- Only correct answers count, and only one correct per user counts toward progress.
- When the server hits a configurable threshold of unique correct users, the bot:
- disables the buttons on the current card
- posts the next quiz
- optionally posts the previous answer for the last card
- Pic Quiz: image attachment + multiple-choice buttons
- Word Quiz: fill-in-the-blank style prompt
- Grammar Quiz: choose the correct form/wording
- Idiom Quiz: guess the correct idiom for a sentence
Each quiz card shows progress like 3/10. The next quiz won’t appear until enough unique users get it right.
Per server (guild), you can configure:
- which channel each quiz type posts to
- threshold per quiz type
- poll interval for checking progress
You import quiz content into SQLite using a provided script:
content_tools/import_json_to_sqlite.py
bot.py # bot entrypoint + help embed + error handler
cogs/ # Discord cogs: setup/admin/health + quiz types
ecafe/ # config, DB utilities, task registry, views
db/schema.sql # SQLite schema
content_tools/ # import script (JSON -> SQLite)
- Quizzes are implemented as cogs inheriting from
BaseQuizCog(cogs/_quiz_base.py). - A central task registry (
ecafe/tasks.py) runs quiz loops per(guild_id, quiz_type). - State and content live in SQLite (
quiz_state,quiz_answered,quiz_content, etc.).
- Python 3.11
discord.py==2.4.0- SQLite (via
aiosqlite) - A Discord bot token + the right intents
Dependencies are pinned in:
requirements.txtenvironments.yaml(conda environment)
- Create an app in the Discord Developer Portal
- Add a Bot user
- Copy the token
Important: this project uses prefix commands (e.g. !ec ...), which require the Message Content Intent.
Enable it in the Developer Portal for your bot.
conda env create -f environments.yaml
conda activate english-cafepython -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
pip install -r requirements.txtCopy .env.example to .env and set values:
ENV=development
DISCORD_TOKEN="your_real_token_here"
DATABASE_PATH=./data/ecafe.db
LOG_LEVEL=INFO
AUTO_START_LOOPS=true
OWNER_IDS=377928910718894091
IMAGES_ROOT=./data/imagesNotes:
DATABASE_PATHdefaults to./data/ecafe.dbif not set.IMAGES_ROOTdefaults todata/images.AUTO_START_LOOPScurrently isn’t wired into startup logic (loops are started via admin commands).
The DB schema is ensured on startup via ensure_db().
The bot reads quiz content from the quiz_content table.
Use the importer:
python content_tools/import_json_to_sqlite.py \
--db ./data/ecafe.db \
--schema ./db/schema.sql \
--image ./path/to/image_data.json \
--word ./path/to/word_data.json \
--grammar ./path/to/grammar_data.json \
--idiom ./path/to/idiom_data.json \
--wipe--wipedeletes existing rows for the types you’re importing, which is usually what you want during iteration.
python bot.pyGenerate an invite URL in the Discord Developer Portal with:
- Scopes:
bot,applications.commands - Permissions:
- Send Messages
- Embed Links
- Attach Files (required for pic quiz images)
- Read Message History
- Use External Emojis (optional)
Also make sure it can post in whichever channels you set for quizzes.
The bot’s command group is !englishcafe with aliases:
!ec, !ecafe, !english, !eng, !engcafe, !english_cafe
Configure where quizzes post and how fast they advance.
!ec set-channel <pic|grammar|word|idiom> <#channel | channel_id | channel_name>
!ec set-threshold <pic|grammar|word|idiom> <positive-int>
!ec set-poll <seconds> (>= 5)
!ec view-settings
Examples:
!ec set-channel pic #english-pic-quiz
!ec set-threshold word 7
!ec set-poll 30
!ec view-settings
Start/stop loops and manage quiz state.
!ec start <pic|grammar|word|idiom|all>
!ec stop <pic|grammar|word|idiom|all>
!ec status
!ec announce <pic|grammar|word|idiom> (posts previous answer if available)
!ec resend <pic|grammar|word|idiom> (reposts active card if any)
!ec clear-answers <pic|grammar|word|idiom> [current|all]
!ec clear-all-answers
!ec version
!ec diag
- Each quiz card has 3 buttons (1 correct + up to 2 distractors).
- Users may click as many times as they want.
- Only a correct click is recorded in
quiz_answered. - The bot checks progress every
poll_seconds. - Once
count_correct >= threshold:- the card’s buttons are disabled
- the loop proceeds to the next quiz
- the bot posts the previous answer card (if this isn’t the first quiz)
Importer expects a JSON object like:
{
"correct_answer_or_key": {
"sentence": "She ____ to the store yesterday.",
"meaning": "Past of go is went.",
"incorrect": ["goes", "gone"]
}
}- The object key is treated as the correct answer (stored as
item_key). incorrectshould be a list of distractor options.
Importer expects an object mapping category-ish keys to lists of items:
{
"animals1_1": [
{
"question_id": 1,
"options": ["Cat", "Dog", "Horse"],
"correct_answer": "Dog",
"img": "quizzes/pic_panic/assets/001.jpg"
}
]
}- The importer derives:
categoryfrom the top-level key (e.g.,animals)hardnessfrom the number segment (mapped to “Level Easy/Hard/…”)
- Images are resolved at runtime using
IMAGES_ROOTand a set of fallback rules (seeecafe/utils.py -> resolve_image_path()).
Tables:
guild_settings: per-server channel IDs + thresholds + poll intervalquiz_state: per-server, per-quiz-type rotation index + active quiz id + last message idquiz_answered: unique correct answers per user per quizquiz_content: JSON payloads for all quiz content
Schema lives in db/schema.sql.
MIT. See LICENSE.