Skip to content

pumpingstationone/ps1_clippy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PS1 Clippy

A helpful Discord bot for Pumping Station One members. Guides users through troubleshooting flows via interactive DM conversations using a "choose your own adventure" style interface.

"It looks like you're locked out! Would you like help with that?" 📎


Table of Contents


Overview

PS1 Clippy is a triage bot that helps Pumping Station One members get assistance through interactive decision trees. The primary use case is helping members who are locked out of the building, but the system is designed to be extensible for any help topic.

How It Works

  1. User runs /lockedout or /clippy in any Discord channel
  2. Bot sends a public acknowledgment and initiates a DM conversation
  3. User navigates through a decision tree using buttons
  4. Bot provides solutions, links to resources, or escalates to human help
  5. Session expires after 30 minutes of inactivity

Relationship to Other PS1 Bots

PS1 Clippy works alongside the existing PS1 bot ecosystem:

Bot Purpose
PS1 Clippy (this bot) Triage layer - guides users to the right solution
Door Bot Handles emergency door access via /door command with TOTP
Role Bot Links Discord accounts to PS1 membership via /authorize
OTP Display ESP32 device that displays the door TOTP code

Clippy doesn't replace Door Bot - it helps users understand when and how to use Door Bot, among other solutions.


Features

Core Features

  • Interactive Help Flows - Choose-your-own-adventure style troubleshooting via DMs
  • Multiple Entry Points - /clippy for general help, /lockedout for lockout-specific help, or DM the bot directly
  • YAML-Defined Flows - Decision trees defined in simple YAML files, no code changes needed
  • Session Management - 30-minute timeout, back navigation, restart option
  • Helper Mode - Staff with the Helper role can trigger flows for other users
  • Escalation - Automatic forum post creation in #help-me when human help is needed
  • Hot Reload - Update flows without restarting the bot via /clippy-admin reload
  • Path Logging - Tracks which paths users take through flows for debugging

User Experience

  • All interactive conversations happen in DMs (private, less intimidating)
  • Public acknowledgment in channel so others know help is coming
  • Back button to revisit previous steps
  • Restart button to start over
  • Clear escape hatches to human help at every step
  • Rich embeds with links and formatted instructions
  • Mobile-friendly button interface

Architecture

System Design

┌─────────────────────────────────────────────────────────────────┐
│                         Discord Server                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  User runs /lockedout          Bot sends DM with buttons       │
│         │                              │                        │
│         ▼                              ▼                        │
│  ┌─────────────┐              ┌─────────────────┐              │
│  │   Channel   │─────────────▶│   DM Channel    │              │
│  │  (public)   │  "Check DMs" │   (private)     │              │
│  └─────────────┘              └────────┬────────┘              │
│                                        │                        │
│                                        ▼                        │
│                               ┌─────────────────┐              │
│                               │  Flow Engine    │              │
│                               │  (YAML-driven)  │              │
│                               └────────┬────────┘              │
│                                        │                        │
│                    ┌───────────────────┼───────────────────┐   │
│                    ▼                   ▼                   ▼   │
│             ┌──────────┐       ┌──────────────┐    ┌──────────┐│
│             │ Solution │       │   Escalate   │    │   End    ││
│             │  (embed) │       │  to #help-me │    │ Session  ││
│             └──────────┘       └──────────────┘    └──────────┘│
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Technology Stack

Component Technology
Language Python 3.12+
Discord Library discord.py 2.4+
Configuration python-dotenv
Flow Definitions YAML (PyYAML)
Deployment Docker
Package Management pip / uv

Quick Start

Prerequisites

Local Installation

  1. Clone the repository:

    git clone https://github.com/pumpingstationone/ps1_clippy.git
    cd ps1_clippy
  2. Create and activate a virtual environment:

    python -m venv .venv
    source .venv/bin/activate  # On Windows: .venv\Scripts\activate
  3. Install dependencies:

    pip install -e .
  4. Configure environment:

    cp .env.example .env
    # Edit .env with your Discord bot token and server IDs
  5. Run the bot:

    python -m ps1_clippy

Docker Installation

# Configure environment
cp .env.example .env
# Edit .env with your settings

# Build and run
docker compose up -d --build

# View logs
docker compose logs -f

# Stop
docker compose down

Configuration

Create a .env file with the following variables:

Variable Required Default Description
DISCORD_BOT_TOKEN Yes - Your Discord bot token
DISCORD_GUILD_ID Yes - Your Discord server ID
DISCORD_HELP_CHANNEL_ID No - Forum channel ID for escalation posts
DISCORD_HELPER_ROLE No Helper Role name or ID that can use admin commands
SESSION_TIMEOUT_MINUTES No 30 How long before inactive sessions expire
LOG_LEVEL No INFO Logging verbosity (DEBUG, INFO, WARNING, ERROR)
FLOWS_GITHUB_REPO No - GitHub repo for flows (format: owner/repo)
FLOWS_GITHUB_BRANCH No main Branch to pull flows from
FLOWS_GITHUB_PATH No flows Path within the repo where flows are stored
GITHUB_TOKEN No - GitHub token (only needed for private repos)

Getting Discord IDs

  1. Enable Developer Mode in Discord: User SettingsApp SettingsAdvancedDeveloper Mode
  2. Right-click on your server → Copy Server ID (this is DISCORD_GUILD_ID)
  3. Right-click on your help forum channel → Copy Channel ID (this is DISCORD_HELP_CHANNEL_ID)

Commands

User Commands

Command Description
/clippy Start the main help menu
/lockedout Start the lockout troubleshooting flow
DM the bot Starts help menu or matches keywords to specific flows

Helper Commands

These require the configured Helper role:

Command Description
/lockedout @user Start lockout flow for another user
/clippy-admin preview Generate Mermaid diagram of flows
/clippy-admin reload [source] [branch] Reload flows (see below)
/clippy-admin status Show current flows source and loaded flows
/clippy-admin check Check if updates are available in the remote repo
/clippy-admin sessions List all active user sessions

Reload Source Options

Source Description
(empty) Reload from configured GitHub repo (or local if not configured)
github Same as empty - reload from configured repo
cache Reload from local cache without fetching (for testing local edits)
local Force reload from local flows/ directory
clear Clear any temporary override, reload from main source
owner/repo Load from a fork as a temporary override (resets on restart)

Testing Flows from a Fork

Content creators can test their changes without affecting production:

  1. Fork the flows repo to your GitHub account
  2. Make changes on a branch
  3. Run /clippy-admin reload yourname/flows-repo feature-branch
  4. Test your changes
  5. Run /clippy-admin reload clear to revert (or just restart the bot)
  6. Once satisfied, open a PR to the main flows repo

Command Behavior

When a user runs /lockedout:

  1. Bot sends a public message in the channel: "📎 I'm here to help you @user! Check your DMs."
  2. Bot opens a DM with the user and sends the first flow step
  3. User navigates using buttons in the DM
  4. Session tracked for 30 minutes

When a Helper runs /lockedout @someone:

  1. Bot sends: "📎 I'm here to help @someone! Check your DMs."
  2. Bot DMs the target user (not the helper)
  3. Useful for helping users who don't know the command

Creating and Editing Flows

Flows are defined in YAML files in the flows/ directory. The bot loads all .yaml files at startup and can reload them on demand.

Flow File Structure

# Metadata about the flow
metadata:
  name: myflow                    # Unique identifier (used in flow:myflow references)
  description: My helpful flow    # Human-readable description
  version: "1.0"                  # Version for tracking changes
  trigger_command: /mycommand     # Optional: dedicated slash command
  trigger_dm_keywords:            # Optional: keywords that trigger this flow via DM
    - help me
    - need assistance

# The decision tree
nodes:
  start:                          # Every flow MUST have a 'start' node
    message: |
      Welcome! How can I help you today?
    buttons:
      - label: "Option A"
        next: node_a
      - label: "Option B"
        next: node_b
        style: secondary

  node_a:
    message: |
      Here's some helpful information about Option A.
    embed:
      title: "Learn More"
      description: |
        Detailed information goes here.

        You can use **markdown** formatting!
      url: "https://example.com"
      color: 0x00ff00             # Green
    buttons:
      - label: "This helped!"
        next: success_end
      - label: "I need more help"
        next: escalate
        action: escalate

  node_b:
    message: |
      Here's information about Option B.
    buttons:
      - label: "Got it!"
        next: success_end

  escalate:
    message: |
      I'll get you some human help.
    action: create_forum_post
    buttons:
      - label: "Thanks!"
        next: end

  success_end:
    message: |
      Glad I could help! Come back anytime.
    action: end

  end:
    message: |
      Thanks for using the help system!
    action: end

Node Properties

Property Required Description
message Yes Text shown to user (supports Discord markdown)
buttons No Array of button choices
embed No Rich embed with title, description, URL, color
action No Special action to perform (see below)

Button Properties

Property Required Description
label Yes Button text (max ~80 characters)
next Yes Node ID to navigate to, or flow:flowname to switch flows
style No primary (blue), secondary (gray), success (green), danger (red)
action No Action to perform when clicked

Special Actions

Action Description
end End the session cleanly
escalate Mark the session as needing escalation (for logging)
create_forum_post Create a post in the #help-me forum channel

Switching Between Flows

Use next: flow:flowname to jump to a different flow:

buttons:
  - label: "I'm actually locked out"
    next: flow:lockedout    # Jumps to lockedout.yaml, starting at 'start' node

The Menu Flow

The _menu.yaml file (note the underscore prefix) is special - it's shown when users:

  • Run /clippy
  • DM the bot without matching any flow keywords

Updating Flows (Local)

  1. Edit the YAML file in flows/
  2. Run /clippy-admin reload local in Discord (or restart the bot)
  3. Changes take effect immediately for new sessions

Updating Flows (GitHub)

When FLOWS_GITHUB_REPO is configured, the bot pulls flows from GitHub:

  1. Push changes to the configured flows repo
  2. Run /clippy-admin reload in Discord
  3. Bot pulls the latest from GitHub and reloads

To check for updates without applying them:

/clippy-admin check

To see what source is currently loaded:

/clippy-admin status

Flow Design Tips

  1. Keep messages concise - Users are often on mobile
  2. Limit buttons - Discord allows max 5 buttons per row, 5 rows total (25 buttons max, but aim for 4-5 per node)
  3. Always provide an escape hatch - Every path should have a "none of these fit" or "need human help" option
  4. Use embeds for links - Embed URLs are more visible than inline links
  5. Test on mobile - The experience should work well on phone screens
  6. Log meaningful paths - Use descriptive node names for easier log analysis

The Lockout Flow

The primary flow helps members who are locked out of the PS1 building.

Decision Tree

START: "What's your situation?"
│
├── "I've paid my dues" / "I'm not sure if I've paid"
│   │
│   └── "Do you have your keyfob?"
│       │
│       ├── "Yes, but where do I tap?"
│       │   └── Instructions on tap location
│       │       └── "Still won't open" → Check membership
│       │
│       ├── "Yes, but door won't open"
│       │   └── Check membership portal
│       │       ├── "Emergency - need in NOW" → Door Bot instructions
│       │       ├── "Everything looks correct" → Escalate to #help-me
│       │       └── "I'm not in PS1 Discord" → Discord guide
│       │
│       ├── "No, I forgot/lost it"
│       │   └── Emergency Door Bot access instructions
│       │
│       └── "I'm new - how do I get a key?"
│           └── New member keyfob instructions
│
└── "None of these fit"
    └── Escalate to #help-me

External Resources Referenced

Resource URL
Membership Portal https://membership.pumpingstationone.org
Emergency Door Bot Access https://wiki.pumpingstationone.org/wiki/Emergency_Door_Bot_Access
New Member Keyfob Guide https://wiki.pumpingstationone.org/wiki/Member_Manual#How_do_I_get_a_key-fob.3F
Discord Server Guide https://wiki.pumpingstationone.org/wiki/Discord
General Contact info@pumpingstationone.org

Session Behavior

Session Lifecycle

  1. Created - When user triggers a flow via command or DM
  2. Active - User navigating through nodes, clicking buttons
  3. Expired - No activity for 30 minutes (configurable)
  4. Ended - User reached an end action or session was manually ended

Re-trigger Behavior

If a user runs /lockedout while they have an active session:

  1. Bot shows a prompt: "You already have an active session. What would you like to do?"
  2. Options: Restart (abandon old session) or Continue where I left off
  3. Choosing "Continue" resends the current node

Navigation

  • Back Button - Appears after the first step, returns to previous node
  • Restart Button - Always available, starts the flow over from the beginning
  • History - Full path is tracked for logging and back navigation

DMs Disabled

If the bot cannot DM the user:

  • Public message in channel: "I can't send DMs to @user. Please enable DMs from server members and try again, or DM me directly!"
  • User can then DM the bot to start the flow

Escalation System

When a user needs human help, the bot can create a forum post in the #help-me channel.

How It Works

  1. User clicks a button with action: create_forum_post
  2. Bot creates a new thread in the configured forum channel
  3. Thread includes:
    • User mention
    • Flow name
    • Last 5 steps of their path (for context)
    • Request for human assistance
  4. User receives a link to the thread

Configuration

Set DISCORD_HELP_CHANNEL_ID to your forum channel's ID. The channel must be a Forum Channel (not a regular text channel).

Example Forum Post

Help request from @username

User: @username
Flow: lockedout
Path taken: [started lockedout] → has_keyfob → check_membership → escalate_help

This user needs human assistance. Please help them out!

Deployment

Docker (Recommended for Production)

The Docker setup mounts the flows/ directory as a volume, so you can edit flows without rebuilding:

# Initial deployment
docker compose up -d --build

# View logs
docker compose logs -f

# After editing flows/
# Just run /clippy-admin reload in Discord - no restart needed!

# After editing Python code
docker compose up -d --build

# Stop
docker compose down

Docker Compose Configuration

services:
  clippy:
    build: .
    container_name: ps1-clippy
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - ./flows:/app/flows:ro    # Flows mounted read-only
    environment:
      - PYTHONUNBUFFERED=1

Manual Deployment

# On your server
cd /path/to/ps1_clippy
source .venv/bin/activate
python -m ps1_clippy

# Or with a process manager like systemd
# Create /etc/systemd/system/ps1-clippy.service

Systemd Service Example

[Unit]
Description=PS1 Clippy Discord Bot
After=network.target

[Service]
Type=simple
User=ps1bot
WorkingDirectory=/opt/ps1_clippy
Environment=PATH=/opt/ps1_clippy/.venv/bin
ExecStart=/opt/ps1_clippy/.venv/bin/python -m ps1_clippy
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Development

Setting Up Development Environment

# Clone and setup
git clone https://github.com/pumpingstationone/ps1_clippy.git
cd ps1_clippy
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

# Run locally
python -m ps1_clippy

Running Tests

pytest

Code Style

The project follows standard Python conventions. Key points:

  • Type hints throughout
  • Dataclasses for models
  • Async/await for Discord operations

Adding a New Flow

  1. Create flows/myflow.yaml following the structure above
  2. Restart the bot or run /clippy-admin reload
  3. The flow is now available via flow:myflow references

Adding a New Slash Command for a Flow

  1. Add trigger_command: /mycommand to your flow's metadata
  2. Register the command in ps1_clippy/bot/client.py:
@self.tree.command(
    name="mycommand",
    description="Description shown in Discord",
)
async def mycommand(interaction: discord.Interaction):
    await self._start_flow(interaction, "myflow")
  1. Restart the bot to register the new command

Design Decisions

These decisions were made during the design and development of PS1 Clippy:

Why a Separate Bot?

PS1 Clippy is separate from Door Bot because:

  • Separation of concerns - Door Bot handles security-critical door access; Clippy handles triage
  • Independent deployment - Can update Clippy without affecting door security
  • Simpler codebase - Each bot does one thing well
  • Matches existing pattern - PS1 already runs multiple specialized bots

Why DMs Instead of Channel Messages?

  • Privacy - Users might not want to publicly announce they're locked out
  • Less noise - Doesn't clutter public channels
  • Better UX - DMs feel more like a conversation with a helper
  • Persistent - Users can scroll back through the conversation

Why YAML for Flows?

  • Non-programmers can edit - Board members and admins can update flows
  • Version controlled - Changes tracked in git, easy to review
  • Hot reloadable - No code deployment needed for content changes
  • Readable - Easy to understand the decision tree structure

Why 30-Minute Timeout?

  • Long enough that users don't feel rushed
  • Short enough that abandoned sessions don't accumulate
  • Matches typical "locked out at the door" scenario duration

Re-trigger Behavior

When a user runs /lockedout with an active session, they're asked whether to restart or continue. This was chosen over:

  • Auto-restart (loses progress)
  • Auto-continue (confusing if they meant to restart)
  • Ignore (frustrating user experience)

Related PS1 Bots

Door Bot

Handles emergency door access using TOTP codes.

Role Bot

Links Discord accounts to PS1 membership.

OTP Display

ESP32 device that displays the door TOTP code.


Discord Bot Setup

If you're setting up a new Discord application:

  1. Go to Discord Developer Portal
  2. Click New Application and name it "PS1 Clippy"
  3. Go to Bot section:
    • Click Reset Token and save it for your .env
    • Enable Message Content Intent under Privileged Gateway Intents
  4. Go to OAuth2URL Generator:
    • Scopes: bot, applications.commands
    • Bot Permissions:
      • Send Messages
      • Send Messages in Threads
      • Create Public Threads
      • Embed Links
      • Use Slash Commands
  5. Use the generated URL to invite the bot to your server

License

MIT License

Copyright (c) 2024 Pumping Station One

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

About

PS1 Discord bot

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •