Polling Package
import "github.com/sw33tLie/bbscope/v2/pkg/polling"
The polling package provides the high-level orchestrator that both the CLI and web server use. It handles the full lifecycle of polling a single platform: listing programs, fetching scopes concurrently, AI normalization with DB caching, upserting to the database, and tracking changes.
API
PollPlatform
func PollPlatform(ctx context.Context, cfg PlatformConfig) (*PlatformResult, error)
Polls a single platform end-to-end. The caller decides how to loop over multiple platforms (sequentially, concurrently, etc.).
PlatformConfig
type PlatformConfig struct {
Poller platforms.PlatformPoller // required
Options platforms.PollOptions // category/bounty/private filters
DB *storage.DB // required
Concurrency int // defaults to 5 if <= 0
Normalizer ai.Normalizer // optional; nil = skip AI
Log Logger // optional; nil = no logging
// Called per-program after upsert (from worker goroutines).
// Nil = no callback.
OnProgramDone func(programURL string, changes []storage.Change, isFirstRun bool)
}
PlatformResult
type PlatformResult struct {
PolledProgramURLs []string // URLs of all successfully polled programs
ProgramChanges []storage.Change // per-program changes accumulated
RemovedProgramChanges []storage.Change // changes from program sync (disabled programs)
IsFirstRun bool // true if DB had 0 programs for this platform
Errors []error // non-fatal errors (fetch failures, DB errors)
}
Logger
type Logger interface {
Infof(format string, args ...interface{})
Warnf(format string, args ...interface{})
Errorf(format string, args ...interface{})
Debugf(format string, args ...interface{})
}
Plug in any logger. Both logrus and stdlib log.Printf wrappers satisfy this interface.
Example: poll HackerOne with streaming output
package main
import (
"context"
"fmt"
"log"
h1 "github.com/sw33tLie/bbscope/v2/pkg/platforms/hackerone"
"github.com/sw33tLie/bbscope/v2/pkg/polling"
"github.com/sw33tLie/bbscope/v2/pkg/storage"
)
type myLogger struct{}
func (myLogger) Infof(f string, a ...interface{}) { log.Printf("[INFO] "+f, a...) }
func (myLogger) Warnf(f string, a ...interface{}) { log.Printf("[WARN] "+f, a...) }
func (myLogger) Errorf(f string, a ...interface{}) { log.Printf("[ERROR] "+f, a...) }
func (myLogger) Debugf(f string, a ...interface{}) {}
func main() {
db, _ := storage.Open("postgres://user:pass@localhost/bbscope?sslmode=disable")
defer db.Close()
poller := h1.NewPoller("user", "token")
result, err := polling.PollPlatform(context.Background(), polling.PlatformConfig{
Poller: poller,
DB: db,
Concurrency: 10,
Log: myLogger{},
OnProgramDone: func(url string, changes []storage.Change, isFirstRun bool) {
if !isFirstRun {
for _, c := range changes {
fmt.Printf("[%s] %s %s\n", c.ChangeType, c.ProgramURL, c.TargetRaw)
}
}
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Polled %d programs, %d changes\n",
len(result.PolledProgramURLs), len(result.ProgramChanges))
}
What PollPlatform does internally
- Checks if this is the first run (DB program count == 0)
- Loads ignored programs from the DB
- Calls
ListProgramHandles()on the platform - Safety check: aborts if 0 handles returned but DB has >10 programs
- Runs a worker pool (
Concurrencygoroutines) that for each program:- Fetches scope via
FetchProgramScope() - Skips ignored programs
- Builds
TargetItemlist from scope elements - Applies AI normalization (loads cached enhancements, only normalizes new items)
- Calls
BuildEntries()andUpsertProgramEntries() - Calls
LogChanges()(skipped on first run) - Calls
OnProgramDonecallback if set
- Fetches scope via
- Calls
SyncPlatformPrograms()to mark removed programs as disabled - Logs changes for removed programs (skipped on first run)
- Returns
PlatformResultwith all accumulated data