218 lines
8.9 KiB
Markdown
218 lines
8.9 KiB
Markdown
---
|
|
phase: 17-telegram-scheduler
|
|
plan: 03
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["17-01", "17-02"]
|
|
files_modified:
|
|
- pkg/bot/handlers.go
|
|
- pkg/bot/handlers_test.go
|
|
autonomous: true
|
|
requirements: [TELE-02, TELE-03, TELE-04, TELE-06]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "/scan triggers engine.Scan and returns masked findings via Telegram"
|
|
- "/verify <id> verifies a specific key and returns result"
|
|
- "/recon runs recon sweep and returns findings"
|
|
- "/status shows uptime, total findings, last scan, active jobs"
|
|
- "/stats shows findings by provider, top 10, last 24h count"
|
|
- "/providers lists loaded provider count and names"
|
|
- "/help shows all available commands with descriptions"
|
|
- "/key <id> sends full unmasked key detail to requesting user only"
|
|
artifacts:
|
|
- path: "pkg/bot/handlers.go"
|
|
provides: "All command handler implementations"
|
|
min_lines: 200
|
|
- path: "pkg/bot/handlers_test.go"
|
|
provides: "Unit tests for handler logic"
|
|
key_links:
|
|
- from: "pkg/bot/handlers.go"
|
|
to: "pkg/engine"
|
|
via: "engine.Scan for /scan command"
|
|
pattern: "eng\\.Scan"
|
|
- from: "pkg/bot/handlers.go"
|
|
to: "pkg/recon"
|
|
via: "reconEngine.SweepAll for /recon command"
|
|
pattern: "SweepAll"
|
|
- from: "pkg/bot/handlers.go"
|
|
to: "pkg/storage"
|
|
via: "db.GetFinding for /key command"
|
|
pattern: "db\\.GetFinding"
|
|
---
|
|
|
|
<objective>
|
|
Implement all Telegram bot command handlers: /scan, /verify, /recon, /status, /stats, /providers, /help, /key. Replace the stubs created in Plan 17-01.
|
|
|
|
Purpose: Makes the bot functional for all TELE-02..06 requirements. Users can control KeyHunter entirely from Telegram.
|
|
Output: pkg/bot/handlers.go with full implementations, pkg/bot/handlers_test.go.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/phases/17-telegram-scheduler/17-CONTEXT.md
|
|
@.planning/phases/17-telegram-scheduler/17-01-SUMMARY.md
|
|
@.planning/phases/17-telegram-scheduler/17-02-SUMMARY.md
|
|
@pkg/engine/engine.go
|
|
@pkg/recon/engine.go
|
|
@pkg/storage/db.go
|
|
@pkg/storage/queries.go
|
|
@pkg/storage/findings.go
|
|
</context>
|
|
|
|
<interfaces>
|
|
<!-- Key interfaces from Plan 17-01 output -->
|
|
From pkg/bot/bot.go (created in 17-01):
|
|
```go
|
|
type Config struct {
|
|
Token string
|
|
AllowedChats []int64
|
|
DB *storage.DB
|
|
ScanEngine *engine.Engine
|
|
ReconEngine *recon.Engine
|
|
ProviderRegistry *providers.Registry
|
|
EncKey []byte
|
|
}
|
|
type Bot struct { cfg Config; bot *telego.Bot; ... }
|
|
func (b *Bot) reply(chatID int64, text string) error
|
|
func (b *Bot) replyPlain(chatID int64, text string) error
|
|
```
|
|
|
|
From pkg/storage/queries.go:
|
|
```go
|
|
func (db *DB) GetFinding(id int64, encKey []byte) (*Finding, error)
|
|
func (db *DB) ListFindingsFiltered(encKey []byte, f Filters) ([]Finding, error)
|
|
```
|
|
|
|
From pkg/engine/engine.go:
|
|
```go
|
|
func (e *Engine) Scan(ctx context.Context, src sources.Source, cfg ScanConfig) (<-chan Finding, error)
|
|
```
|
|
|
|
From pkg/recon/engine.go:
|
|
```go
|
|
func (e *Engine) SweepAll(ctx context.Context, cfg Config) ([]Finding, error)
|
|
```
|
|
</interfaces>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Implement /scan, /verify, /recon command handlers</name>
|
|
<files>pkg/bot/handlers.go</files>
|
|
<action>
|
|
Create pkg/bot/handlers.go (replace stubs from bot.go). All handlers are methods on *Bot.
|
|
|
|
**handleScan(bot *telego.Bot, msg telego.Message):**
|
|
- Parse path from message text: `/scan /path/to/dir` (whitespace split, second arg)
|
|
- If no path provided, reply with usage: "/scan <path>"
|
|
- Check rate limit (60s cooldown)
|
|
- Reply "Scanning {path}..." immediately
|
|
- Create sources.FileSource for the path
|
|
- Run b.cfg.ScanEngine.Scan(ctx, src, engine.ScanConfig{Workers: runtime.NumCPU()*4})
|
|
- Collect findings from channel
|
|
- Format response: "Found {N} potential keys:\n" + each finding as "- {provider}: {masked_key} ({confidence})" (max 20 per message, truncate with "...and N more")
|
|
- If 0 findings: "No API keys found in {path}"
|
|
- Always use masked keys — never send raw values
|
|
|
|
**handleVerify(bot *telego.Bot, msg telego.Message):**
|
|
- Parse key ID from message: `/verify <id>` (parse int64)
|
|
- If no ID, reply usage: "/verify <key-id>"
|
|
- Check rate limit (60s cooldown)
|
|
- Look up finding via b.cfg.DB.GetFinding(id, b.cfg.EncKey)
|
|
- If not found, reply "Key #{id} not found"
|
|
- Run verify.NewHTTPVerifier(10s).Verify against the finding using provider spec from registry
|
|
- Reply with: "Key #{id} ({provider}):\nStatus: {verified|invalid|error}\nHTTP: {code}\n{metadata if any}"
|
|
|
|
**handleRecon(bot *telego.Bot, msg telego.Message):**
|
|
- Parse query from message: `/recon <query>` (everything after /recon)
|
|
- If no query, reply usage: "/recon <search-query>"
|
|
- Check rate limit (60s cooldown)
|
|
- Reply "Running recon for '{query}'..."
|
|
- Run b.cfg.ReconEngine.SweepAll(ctx, recon.Config{Query: query})
|
|
- Format response: "Found {N} results:\n" + each as "- [{source}] {url} ({snippet})" (max 15 per message)
|
|
- If 0 results: "No results found for '{query}'"
|
|
|
|
**All handlers:** Wrap in goroutine so the update loop is not blocked. Use context.WithTimeout(ctx, 5*time.Minute) to prevent runaway scans.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/salva/Documents/apikey && go build ./pkg/bot/...</automated>
|
|
</verify>
|
|
<done>/scan, /verify, /recon handlers compile and call correct engine methods.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Implement /status, /stats, /providers, /help, /key handlers and tests</name>
|
|
<files>pkg/bot/handlers.go, pkg/bot/handlers_test.go</files>
|
|
<action>
|
|
Add to pkg/bot/handlers.go:
|
|
|
|
**handleStatus(bot *telego.Bot, msg telego.Message):**
|
|
- Query DB for total findings count: `SELECT COUNT(*) FROM findings`
|
|
- Query last scan time: `SELECT MAX(finished_at) FROM scans`
|
|
- Query active scheduled jobs: `SELECT COUNT(*) FROM scheduled_jobs WHERE enabled=1`
|
|
- Bot uptime: track start time in Bot struct, compute duration
|
|
- Reply: "Status:\n- Findings: {N}\n- Last scan: {time}\n- Active jobs: {N}\n- Uptime: {duration}"
|
|
|
|
**handleStats(bot *telego.Bot, msg telego.Message):**
|
|
- Query findings by provider: `SELECT provider_name, COUNT(*) as cnt FROM findings GROUP BY provider_name ORDER BY cnt DESC LIMIT 10`
|
|
- Query findings last 24h: `SELECT COUNT(*) FROM findings WHERE created_at > datetime('now', '-1 day')`
|
|
- Reply: "Stats:\n- Top providers:\n 1. {provider}: {count}\n ...\n- Last 24h: {count} findings"
|
|
|
|
**handleProviders(bot *telego.Bot, msg telego.Message):**
|
|
- Get provider list from b.cfg.ProviderRegistry.List()
|
|
- Reply: "Loaded {N} providers:\n{comma-separated list}" (truncate if >4096 chars Telegram message limit)
|
|
|
|
**handleHelp(bot *telego.Bot, msg telego.Message):**
|
|
- Static response listing all commands:
|
|
"/scan <path> - Scan files for API keys\n/verify <id> - Verify a specific key\n/recon <query> - Run OSINT recon\n/status - Show system status\n/stats - Show finding statistics\n/providers - List loaded providers\n/key <id> - Show full key detail (DM only)\n/subscribe - Enable auto-notifications\n/unsubscribe - Disable auto-notifications\n/help - Show this help"
|
|
|
|
**handleKey(bot *telego.Bot, msg telego.Message):**
|
|
- Parse key ID from `/key <id>`
|
|
- If no ID, reply usage
|
|
- Check message is from private chat (msg.Chat.Type == "private"). If group chat, reply "This command is only available in private chat for security"
|
|
- Look up finding via db.GetFinding(id, encKey) — this returns UNMASKED key
|
|
- Reply with full detail: "Key #{id}\nProvider: {provider}\nKey: {full_key_value}\nSource: {source_path}:{line}\nConfidence: {confidence}\nVerified: {yes/no}\nFound: {created_at}"
|
|
- This is the ONLY handler that sends unmasked keys
|
|
|
|
**Tests in pkg/bot/handlers_test.go:**
|
|
- TestHandleHelp_ReturnsAllCommands: Verify help text contains all command names
|
|
- TestHandleKey_RejectsGroupChat: Verify /key in group chat returns security message
|
|
- TestFormatFindings_TruncatesAt20: Create 30 mock findings, verify formatted output has 20 entries + "...and 10 more"
|
|
- TestFormatStats_EmptyDB: Verify stats handler works with no findings
|
|
|
|
For tests, create a helper that builds a Bot with :memory: DB and nil engines (for handlers that only query DB).
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/salva/Documents/apikey && go test ./pkg/bot/... -v -count=1</automated>
|
|
</verify>
|
|
<done>All 8 command handlers implemented. /key restricted to private chat. Tests pass for help, key security, truncation, empty stats.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `go build ./pkg/bot/...` compiles
|
|
- `go test ./pkg/bot/... -v` passes all tests
|
|
- All 8 commands have implementations (no stubs remain)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- /scan triggers engine scan and returns masked findings
|
|
- /verify looks up and verifies a key
|
|
- /recon runs SweepAll
|
|
- /status, /stats, /providers, /help return informational responses
|
|
- /key sends unmasked detail only in private chat
|
|
- All output masks keys except /key in DM
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/17-telegram-scheduler/17-03-SUMMARY.md`
|
|
</output>
|