A Discord bot that runs a Spyfall-style party game in a dedicated channel: players join, roles are assigned (Spy vs Civilian), civilians share a secret location, spies try to infer it, and everyone votes to eliminate suspected spies.
If you like social deduction, chaos, and watching humans pretend they’re good at lying, you’re in the right place.
Game loop (high level):
- Join Phase: A join embed is posted with a persistent Join / Leave button.
- Role Assignment: Players are assigned as Spy or Civilian and a secret Location is chosen.
- Reveal Phase: Players privately reveal their role (civilians see the location, spies do not).
- Discussion Phase:
- Players speak in turn (timed).
- Then there’s an open discussion window.
- Spies can attempt an instant win by guessing the location in chat.
- Voting Phase: Players vote on who they think is the spy.
- Ties trigger a short tie-breaking discussion + revote.
- Win Conditions:
- Spy Majority (spies outnumber civilians) → spies win.
- All spies eliminated → civilians win.
- Spy guesses location correctly → spies win immediately.
- No one votes → spies win by default (humans, impressive).
All commands only work in your configured Find the Spy channel.
-
!find_the_spy start(alias:!fts start)
Starts a new join window. -
!find_the_spy end(alias:!fts end)
Ends the current game (authorized user only). -
!end
Optional global alias for ending the game (authorized user only).
In the Discord Developer Portal:
- Create an application → add a bot
- Copy the bot token
- Under Privileged Gateway Intents, enable:
- Message Content Intent (required by this bot)
Copy the example env file and set your token:
cp .env.example .envEdit .env and set:
DISCORD_TOKEN="your_token_here"Edit config.py:
FIND_THE_SPY_CHANNEL_ID= the channel where the game runsAUTHORIZED_USER_ID= the Discord user ID allowed toendgames quickly- Timers and
MIN_PLAYERSare also configured here
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
pip install -r requirements.txtpython bot.pyThe bot persists join state to JSON in:
games/find_the_spy/storage/fts_state_<guild>_<channel>.json
This is used to resume/continue a join window if the bot restarts, and to keep the join embed’s player list in sync.
bot.py– entry point, bot startup, registers cog + persistent join viewcog.py– command group (!fts ...) and admin end logicstate.py– in-memory game state and phase constantsstorage.py– JSON persistence helpers (atomic writes)utils.py– helpers for game instance, task tracking, permission cleanupphases/– phase controllers for join/reveal/discussion/voteservices/– embeds, messaging, permissions, timers, victory handling
- Permissions: the bot changes channel overrides to enforce timed speaking. Make sure it has:
- Manage Channels / Manage Roles (or enough to set channel permission overwrites)
- Send Messages / Embed Links / Read Message History
- Intents: if Message Content Intent is not enabled in the portal, timed speaking (waiting for messages) won’t work reliably.
- Multiple games: the code keeps one game per guild in memory (
GAMESinstate.py). - State files:
games/find_the_spy/storage/*.jsonare generated and should not be committed (already ignored in.gitignore).
- Type
!fts start - Click Join / Leave
- Reveal your role when prompted
- Talk when it’s your turn
- Vote like you’re not biased
MIT (see LICENSE).