Skip to content

Repository files navigation

Karma Bot

A Discord bot for giving and tracking karma points across server members and teams.

How It Works

Giving Karma

Mention a user followed by ++ (two or more plus signs) to give them karma:

@alice +++        # gives Alice 3 karma
@bob +++++        # gives Bob 5 karma

Removing Karma

Use -- (two or more minus signs) or em dashes to remove karma:

@alice ---        # removes 3 karma
@alice —          # removes 2 karma (em dash counts as 2)
@alice ———        # removes 6 karma

Team Karma

Mention a team role to give karma to all members of that team:

@Frontend +++     # gives +3 karma to every member with the Frontend role

Team role karma allows self-karma (if you're on the team) and bypasses the global rate limit, but per-user limits still apply.

Rate Limits

  • Global limit — max total karma you can give/take in a rolling interval (LIMIT_MAX_KARMA_SEND / LIMIT_MIN_KARMA_SEND)
  • Per-user limit — max karma you can give/take to the same person per interval (LIMIT_MAX_KARMA_USER_SEND / LIMIT_MIN_KARMA_USER_SEND)
  • The bot tells you exactly why karma was capped (per-user limit, global budget, or rate limited with reset time)

Karma Windows

Karma accumulates in configurable time-based windows (semiannual by default). When a window ends, the leaderboard is automatically posted and a new window begins. Historical windows are preserved.

Commands

Karma

Command Description
/leaderboard View the karma leaderboard (with period selector dropdown)
/team-leaderboard View the team karma leaderboard
/ping Check bot latency

Configuration (requires Manage Server)

Command Description
/config set-leaderboard-channel #channel Set where leaderboards are posted on window rollover
/config add-karma-role @role Add a role that can give and receive karma
/config remove-karma-role @role Remove a karma role
/config view-karma-roles List all karma roles
/config add-team-role @role Add a role as a team for the team leaderboard
/config remove-team-role @role Remove a team role
/config view-team-roles List all team roles

When no karma roles are configured, everyone can use the bot.

Project Structure

src/
  bot.py                              # Entry point, error handler, persistent views
  core/
    config.py                         # Rate limits, karma window rrule
    checks.py                         # @has_karma_role() decorator + member check
    emojis.py                         # Custom Discord emoji constants
    functions/karma.py                # Leaderboard embed builders
    message_utils/paginator.py        # Persistent paginated embeds
  backend/
    db.py                             # Database singleton (async SQLModel + asyncpg)
    cache.py                          # Stale-while-revalidate cache manager
    models.py                         # User, KarmaWindow, KarmaTransaction, GuildConfig, UserTeamRole
    tables/
      base.py                         # BaseDB[T] — generic CRUD
      db_user.py                      # UserDB — increment_karma
      db_karma_window.py              # KarmaWindowDB — leaderboard + window queries
      db_karma_transaction.py         # KarmaTransactionDB — rate limit queries
      db_guild_config.py              # GuildConfigDB — per-guild settings (cached)
      db_user_team_role.py            # UserTeamRoleDB — team role cache + aggregation
  cogs/
    commands/
      general.py                      # /ping
      karma.py                        # Karma listener, /leaderboard, /team-leaderboard
      config.py                       # /config command group
    workers/
      karma_history.py                # Auto-post leaderboard on window rollover
tests/                                # 61 tests (in-memory SQLite, no Docker needed)

Setup

Prerequisites

  • Python 3.12+
  • Docker & Docker Compose (for the database)

Development

cp .env.example .env
# Edit .env (DISCORD_TOKEN is required).
# DATABASE_URL is optional: if omitted, a local Postgres container is started automatically.

chmod +x dev.sh
./dev.sh --build        # Linux/macOS
./dev.ps1 --build       # Windows (PowerShell)

Both scripts check .env for DATABASE_URL:

  • Present -> docker compose up -d (uses your external database)
  • Absent -> docker compose --profile local-db up -d (starts a bundled postgres:16-alpine container at localhost:5432)

To run the bot directly (Postgres must already be running and DATABASE_URL set):

pip install -r requirements.txt
python -m src.bot

Environment Variables

Variable Required Description
DISCORD_TOKEN Yes Your Discord bot token
DATABASE_URL No (dev) PostgreSQL connection string; omit to use the local-db container

Discord Developer Portal

Enable these Privileged Gateway Intents for your bot:

  • Message Content Intent — needed to read the ++/-- karma pattern
  • Server Members Intent — needed for guild.get_member() in leaderboards

Testing

Tests use in-memory SQLite — no database setup required.

pip install -r requirements.txt
python -m pytest tests/ -v

Configuration

Edit src/core/config.py to adjust rate limits:

Setting Default Description
LIMIT_MAX_KARMA_SEND 100 Max total karma a user can give per interval
LIMIT_MIN_KARMA_SEND -100 Max total karma a user can remove per interval
LIMIT_KARMA_SEND_INTERVAL 6 hours Rolling window for the global rate limit
LIMIT_MAX_KARMA_USER_SEND 5 Max karma a user can give to one person per interval
LIMIT_MIN_KARMA_USER_SEND -5 Max karma a user can remove from one person per interval
LIMIT_KARMA_USER_SEND_INTERVAL 6 hours Rolling window for the per-user rate limit
KARMA_WINDOW_RULE Semiannual (Jan/Jul) dateutil.rrule defining karma reset periods

Changing the Karma Window Period

The KARMA_WINDOW_RULE accepts any dateutil.rrule:

from dateutil.rrule import MONTHLY, WEEKLY, MO, rrule

# Semiannual (default)
KARMA_WINDOW_RULE = rrule(MONTHLY, interval=6, bymonth=(1, 7), dtstart=...)

# Fortnightly starting Monday
KARMA_WINDOW_RULE = rrule(WEEKLY, interval=2, byweekday=MO, dtstart=...)

# Monthly on the 1st
KARMA_WINDOW_RULE = rrule(MONTHLY, dtstart=...)

Architecture

  • SQLModel — typed models that double as Pydantic + SQLAlchemy
  • asyncpg — async PostgreSQL driver
  • BaseDB[T] — generic CRUD base class; per-table classes add domain methods
  • cached() — stale-while-revalidate wrapper for any table (used on guild_config)
  • Singleton patternfrom src.backend.tables import user, karma_window and use directly
  • Persistent paginator — embed buttons survive bot restarts via PersistentPaginatorView

Deployment

Tag a release to trigger the Docker build and push to GitHub Container Registry:

git tag v1.0.0
git push origin v1.0.0

Production uses docker-compose.prod.yml, which adds a managed Postgres service, restart: always, log rotation, and injects DATABASE_URL from the host environment:

POSTGRES_PASSWORD=<secret> docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Contributing

  1. Fork the repository and create a branch from main
  2. Install dependencies and set up pre-commit hooks:
    pip install -r requirements.txt
    pre-commit install --hook-type commit-msg
  3. Make your changes and ensure tests pass:
    python -m pytest tests/ -v
  4. Commit using Conventional Commits (enforced by pre-commit)
  5. Open a pull request against main

About

A Discord bot that lets server members give each other karma points via mentions.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages