8.9 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 17-telegram-scheduler | 03 | execute | 2 |
|
|
true |
|
|
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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 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:
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:
func (e *Engine) Scan(ctx context.Context, src sources.Source, cfg ScanConfig) (<-chan Finding, error)
From pkg/recon/engine.go:
func (e *Engine) SweepAll(ctx context.Context, cfg Config) ([]Finding, error)
*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 "
- 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 "
- 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 "
- 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. cd /home/salva/Documents/apikey && go build ./pkg/bot/... /scan, /verify, /recon handlers compile and call correct engine methods.
Task 2: Implement /status, /stats, /providers, /help, /key handlers and tests pkg/bot/handlers.go, pkg/bot/handlers_test.go 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 - Scan files for API keys\n/verify - Verify a specific key\n/recon - Run OSINT recon\n/status - Show system status\n/stats - Show finding statistics\n/providers - List loaded providers\n/key - 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). cd /home/salva/Documents/apikey && go test ./pkg/bot/... -v -count=1 All 8 command handlers implemented. /key restricted to private chat. Tests pass for help, key security, truncation, empty stats.
- `go build ./pkg/bot/...` compiles - `go test ./pkg/bot/... -v` passes all tests - All 8 commands have implementations (no stubs remain)<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>