Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Tests & Coverage

on:
push:
branches:
- main
pull_request:
branches:
- main

permissions:
contents: read

jobs:
test:
name: Test & Coverage
runs-on: ubuntu-latest
permissions:
contents: read
actions: read # pento/lcov-coverage-check: list workflow runs and download baseline artifact
pull-requests: write # pento/lcov-coverage-check: post/update coverage comment on PRs

steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: audit

- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install tools (Bun + Gitleaks via mise)
run: ./bin/mise install

- name: Install dependencies
run: ./bin/mise run install

- name: Type check
run: ./bin/mise run typecheck

- name: Run tests with coverage
run: ./bin/mise run test:coverage

# Stores lcov-baseline artifact on main; retrieves it and posts a coverage
# diff comment on PRs. Falls back to summary-only on first run or fork PRs.
- name: Report coverage
uses: pento/lcov-coverage-check@972428b8d5b3fbd8230df23ca4190ecce29979cd # v3.1.0
with:
path: "src/"
github-token: ${{ secrets.GITHUB_TOKEN }}
8 changes: 8 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ run = "bun run build"
description = "Type-check TypeScript source without emitting files"
run = "bun run typecheck"

[tasks.test]
description = "Run all tests"
run = "bun test"

[tasks."test:coverage"]
description = "Run all tests with LCOV coverage report (output: coverage/lcov.info)"
run = "bun test --coverage --coverage-reporter=lcov"

[tasks."prod:build"]
description = "Build minified production executable"
run = "bun run build"
Expand Down
53 changes: 53 additions & 0 deletions IDEAS/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Ideas & TODO

Feature ideas and potential improvements for the bot.

Priority is rated from an end-user perspective:
- 🔴 **High** — directly improves core daily usage; most users would benefit immediately
- 🟡 **Medium** — noticeable improvement but not critical to the main workflow
- 🟢 **Low** — nice to have, niche use case, or primarily an admin/backend concern

---

## Code & Redemption

- 🟡 **`/stats` command** — Server-wide stats: total codes found, redemption counts, registered user count, aggregate loot earned. Data already exists in DB.
- 🟡 **Loot summary in `/codes`** — Show aggregate totals (gold, rubies, equipment) across all redeemed codes. `loot_detail` JSON is already stored per row.
- 🟢 **Code source tracking** — Store which channel/message ID a code was found in. Useful for auditing and showing users where codes originated.
- 🔴 **Multi-channel scanning** — `DISCORD_CHANNEL_ID` is a single channel. Support a comma-separated list or a `/setchannels` admin command. If codes get posted in other channels, users miss them entirely.
- 🟢 **Auto-purge expired pending codes** — Pending codes that fail globally should be cleaned up automatically; right now they stay in `pending_codes` forever.
- 🔴 **Scheduled catchup** — Run catchup automatically for all autoredeem-enabled users on a configurable timer (e.g. daily) as a safety net for missed codes. Prevents silently missing free loot.

---

## Notifications & UX

- 🔴 **DM notifications for new codes** — Opt-in per user to get a DM when a code is *detected*, independent of whether autoredeem is on. Users who want to redeem manually currently have no way to know a code appeared.
- 🔴 **Notification preferences command** — Let users configure: DM on success, DM on failure, DM when a code they haven't claimed is about to expire. High value as DM spam is a common complaint with bots.
- 🔴 **Better blacksmith UX** — `/blacksmith` requires a raw hero ID. Inventory data from `getUserDetails` includes hero names — show a hero picker or list. Current UX is nearly unusable without looking up IDs externally.
- 🟢 **Paginated `/codes`** — Replace the `count` cap (currently max 20) with Discord prev/next buttons for a cleaner experience.

---

## Admin & Operations

- 🟢 **`/admin` subcommand group** — Consolidate admin actions under one command:
- View recent audit log entries
- Force-expire or remove a specific code
- Remove a specific pending code
- View registered user count
- 🟢 **Config command** — Set bot config (scan channels, log level, autoredeem default) at runtime without restarting.

---

## API / Game Features

- 🟡 **`/buy` command** — `purchaseChests()` is already implemented in `idleChampionsApi.ts`. Just needs a command wired up. Convenient to do from Discord without opening the game.
- 🔴 **`/heroes` command** — List the user's champions with levels and upgrade costs from the player data response. Directly unblocks the blacksmith UX problem above.
- 🟡 **`/export` command** — Export redeemed code history as a CSV file attachment (`AttachmentBuilder`). Useful for personal tracking.

---

## Quality of Life

- ✅ **`/deleteaccount` command** *(implemented)* — Let users remove their credentials and history from the DB (GDPR-friendly self-service).
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ See [docker-compose.example.yml](docker-compose.example.yml) for all available c

## ✨ Features

- 🤖 **Slash Commands** - `/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/help`
- 🤖 **Slash Commands** - `/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/deleteaccount`, `/help`
- 🔄 **Auto Code Detection** - Scans Discord messages for codes automatically
- ⏮️ **Message History Backfill** - Recover missed codes from message history with built-in rate limiting
- 🔁 **Catch Up** - Redeem all known valid codes in one command (great for new members)
Expand All @@ -93,6 +93,7 @@ See [docker-compose.example.yml](docker-compose.example.yml) for all available c
- 📦 **Chest Management** - Open chests and view loot
- ⚒️ **Blacksmith** - Upgrade heroes with contracts
- 📊 **Inventory** - View gold, rubies, equipment, and progress
- 🗑️ **Account Deletion** - GDPR-friendly self-service data removal (`/deleteaccount`)
- 💾 **Secure Storage** - SQLite database keeps credentials safe and local
- 👥 **Multi-User** - Each user manages their own account
- ⚡ **Fast** - Built on Bun for 3-4x performance vs Node.js
Expand Down
55 changes: 52 additions & 3 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The Discord bot responds to slash commands in Discord channels. All commands ret

**Response Format**: Discord Embeds (rich message format) or Ephemeral Text

**Total commands**: 11 (`/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/help`)
**Total commands**: 12 (`/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/deleteaccount`, `/help`)

---

Expand Down Expand Up @@ -708,7 +708,53 @@ Bot (ephemeral): ⏸️ Auto-Redeem Disabled

---

### 11. `/help`
### 11. `/deleteaccount`

Permanently delete all data the bot holds about the invoking user.

**Invocation**:
```
/deleteaccount
```

**Parameters**: None

**Flow**:
1. Bot checks whether credentials or backfill history exist for the user. If none are found, responds with a warning and exits.
2. Bot checks whether a backfill the user initiated is currently in progress; if so, refuses deletion until it completes.
3. Bot sends an ephemeral embed with Yes / Cancel buttons (30-second timeout).
4. If the user clicks **Yes, delete everything**: credentials, all redeemed code records, audit log entries, and backfill operation history are permanently removed. A summary is shown.
5. If the user clicks **Cancel** or the timeout elapses: no data is changed.

**Data removed on confirmation**:
- `users` row (credentials, server, autoredeem preference)
- All `redeemed_codes` rows for the user
- All `audit_log` rows for the user
- All `backfill_operations` rows for the user

**Error Codes**:

| Code | Meaning | Resolution |
|------|---------|------------|
| `NO_ACCOUNT` | No credentials or backfill history stored for this user | Nothing to delete |
| `BACKFILL_IN_PROGRESS` | A backfill the user initiated is still running | Wait for the backfill to complete, then retry |

**Example**:
```
User: /deleteaccount
Bot (ephemeral): ⚠️ Delete Account — Are you sure?
[Yes, delete everything] [Cancel]

User: clicks Yes
Bot (ephemeral): ✅ Account Deleted
• Credentials deleted
• 42 code record(s) deleted
• Audit log entries deleted
```

---

### 12. `/help`

Display command reference and usage instructions.

Expand Down Expand Up @@ -752,6 +798,8 @@ Display command reference and usage instructions.
│ Toggle automatic code redemption │
│ /backfill [channel:<channel>] │
│ Recover codes from message history │
│ /deleteaccount │
│ Permanently delete all your stored data │
│ /help │
│ Show this message │
│ │
Expand Down Expand Up @@ -948,6 +996,7 @@ The bot implements request throttling:
| `/open` | 1 per 3 seconds | Per user |
| `/blacksmith` | 1 per 3 seconds | Per user |
| `/backfill` | 1 concurrent | Per guild |
| `/deleteaccount` | Unlimited | Per user |
| Code detection | Per message | Automatic |

---
Expand Down Expand Up @@ -1195,7 +1244,7 @@ Bot (embed):
## OSPS-SA-02.01 Compliance

✅ **Software Interfaces Documented**:
- 11 slash commands with parameters, responses, error codes
- 12 slash commands with parameters, responses, error codes
- Message event detection with pattern matching
- Response formats and data structures
- Error handling and recovery procedures
Expand Down
20 changes: 19 additions & 1 deletion docs/full-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A Discord bot that automatically scans for and redeems Idle Champions promo code

## Features

- 🤖 **Slash Commands** - `/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/help`
- 🤖 **Slash Commands** - `/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/deleteaccount`, `/help`
- 🔄 **Auto Code Detection** - Scans Discord messages for codes automatically
- 🤖 **Auto-Redeem Toggle** - Enable or disable automatic code redemption per user (`/autoredeem`)
- ⏮️ **Message History Backfill** - Recover missed codes from message history (protected with rate limiting)
Expand All @@ -13,6 +13,7 @@ A Discord bot that automatically scans for and redeems Idle Champions promo code
- ⚒️ **Blacksmith** - Upgrade heroes with contracts
- 📊 **Inventory** - View gold, rubies, equipment, and progress
- 💾 **Secure Storage** - SQLite database keeps credentials safe and local
- 🗑️ **Account Deletion** - GDPR-friendly self-service removal of all stored data (`/deleteaccount`)
- 👥 **Multi-User** - Each user manages their own account
- ⚡ **Fast** - Built on Bun for 3-4x performance vs Node.js

Expand Down Expand Up @@ -71,6 +72,7 @@ brew install mise
| `/codes [count:<count>]` | Show your redeemed codes history (last 10) |
| `/makepublic code:<code>` | Share one of your redeemed codes with other users |
| `/backfill [channel:<channel>]` | Recover missed codes from message history |
| `/deleteaccount` | Permanently delete all your stored data (GDPR) |
| `/help` | Show all commands |

### Setup & Authentication
Expand Down Expand Up @@ -202,6 +204,22 @@ Toggle whether the bot automatically redeems new codes when they are detected in
- **Behaviour when disabled:** Codes are still detected and stored, but you must use `/redeem` or `/catchup` to claim them manually
- **Example:** `/autoredeem enabled:off`

### Account Management

#### `/deleteaccount`

Permanently and irreversibly delete all data the bot holds about you. Requires an explicit confirmation step to prevent accidents.

- **No parameters required**
- **What is deleted:**
- Your Idle Champions credentials (user ID + hash)
- Your full code redemption history
Comment thread
BigMichi1 marked this conversation as resolved.
- Your audit log entries
- Your backfill operation history
- **Confirmation:** A Yes / Cancel button prompt appears with a 30-second timeout — no action is taken unless you click **Yes, delete everything**
- **After deletion:** You will need to run `/setup` again to use the bot
- **Example:** `/deleteaccount`

### Help

#### `/help`
Expand Down
5 changes: 3 additions & 2 deletions docs/structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ idle-code-redeemer/
│ ├── bot/ ← Discord bot (ACTIVE)
│ │ ├── bot.ts ← Main Discord client & event handlers
│ │ ├── api/ ← Game server API client
│ │ ├── commands/ ← Slash command handlers (11 commands)
│ │ ├── commands/ ← Slash command handlers (12 commands)
│ │ ├── database/ ← Database managers & Drizzle schema
│ │ │ ├── db.ts ← Drizzle connection & migrate()
│ │ │ ├── userManager.ts
Expand Down Expand Up @@ -73,7 +73,7 @@ idle-code-redeemer/
- **[src/bot/bot.ts](../src/bot/bot.ts)** - Discord client initialization, event handlers, command routing
- **[src/bot/api/idleChampionsApi.ts](../src/bot/api/idleChampionsApi.ts)** - Game server API client with query-parameter format

### Commands (11 slash commands)
### Commands (12 slash commands)

- **[src/bot/commands/setup.ts](../src/bot/commands/setup.ts)** - `/setup user_id:<id> user_hash:<hash>`
- **[src/bot/commands/redeem.ts](../src/bot/commands/redeem.ts)** - `/redeem code:<code>`
Expand All @@ -85,6 +85,7 @@ idle-code-redeemer/
- **[src/bot/commands/codes.ts](../src/bot/commands/codes.ts)** - `/codes [count:<count>]` (view redeemed codes history)
- **[src/bot/commands/makepublic.ts](../src/bot/commands/makepublic.ts)** - `/makepublic code:<code>` (share codes with other users)
- **[src/bot/commands/backfill.ts](../src/bot/commands/backfill.ts)** - `/backfill [channel:<channel>]` (recover missed codes)
- **[src/bot/commands/deleteaccount.ts](../src/bot/commands/deleteaccount.ts)** - `/deleteaccount` (permanently delete all stored user data, GDPR-friendly)
- **[src/bot/commands/help.ts](../src/bot/commands/help.ts)** - `/help`

### Database
Expand Down
3 changes: 2 additions & 1 deletion docs/system-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ The bot reads promo codes from Discord messages, redeems them via the Idle Champ
**Role**: End-user interacting with the bot

**Actions**:
- Submit slash commands (`/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/help`)
- Submit slash commands (`/setup`, `/redeem`, `/catchup`, `/autoredeem`, `/inventory`, `/open`, `/blacksmith`, `/codes`, `/makepublic`, `/backfill`, `/deleteaccount`, `/help`)
- Send messages containing promo codes in the monitored channel
- Receive responses and error messages from the bot

Expand Down Expand Up @@ -508,6 +508,7 @@ All handlers follow pattern:
| `/codes` | codes.ts | View code history | Embed |
| `/makepublic` | makepublic.ts | Share codes with users | Message |
| `/backfill` | backfill.ts | Scan message history | Embed |
| `/deleteaccount` | deleteaccount.ts | Delete all user data (GDPR) | Ephemeral |
| `/help` | help.ts | Command reference | Embed |

---
Expand Down
2 changes: 2 additions & 0 deletions src/bot/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import logger from './utils/logger';
import { apiRequestLogger } from './utils/apiRequestLogger';
import * as backfillCommand from './commands/backfill';
import * as blacksmithCommand from './commands/blacksmith';
import * as deleteaccountCommand from './commands/deleteaccount';
import * as catchupCommand from './commands/catchup';
import * as codesCommand from './commands/codes';
import * as autoredeemCommand from './commands/autoredeem';
Expand Down Expand Up @@ -54,6 +55,7 @@ const commands = [
blacksmithCommand,
catchupCommand,
codesCommand,
deleteaccountCommand,
helpCommand,
inventoryCommand,
makepublicCommand,
Expand Down
Loading
Loading