package maigret import ( "context" "os/exec" "regexp" "strconv" "strings" "github.com/anotherhadi/iknowyou/internal/tools" ) const ( name = "maigret" description = "Username OSINT across 3000+ sites. Searches social networks, forums, and online platforms for an account matching the target username." link = "https://github.com/soxoj/maigret" icon = "" ) var accountsRe = regexp.MustCompile(`returned (\d+) accounts`) type Config struct { AllSites bool `yaml:"all_sites" iky:"desc=Scan all sites in the database instead of just the top 500 (slower);default=false"` } type Runner struct { cfg Config } func New() tools.ToolRunner { cfg := Config{} tools.ApplyDefaults(&cfg) return &Runner{cfg: cfg} } func (r *Runner) Name() string { return name } func (r *Runner) Description() string { return description } func (r *Runner) Link() string { return link } func (r *Runner) Icon() string { return icon } func (r *Runner) InputTypes() []tools.InputType { return []tools.InputType{tools.InputTypeUsername} } func (r *Runner) ConfigPtr() interface{} { return &r.cfg } func (r *Runner) ConfigFields() []tools.ConfigField { return tools.ReflectConfigFields(r.cfg) } func (r *Runner) Available() (bool, string) { if _, err := exec.LookPath("maigret"); err != nil { return false, "maigret binary not found in PATH" } return true, "" } func (r *Runner) Dependencies() []string { return []string{"maigret"} } func (r *Runner) Run(ctx context.Context, target string, _ tools.InputType, out chan<- tools.Event) error { defer close(out) args := []string{"--no-progressbar", target} if r.cfg.AllSites { args = append(args, "-a") } cmd := exec.CommandContext(ctx, "maigret", args...) output, err := tools.RunWithPTY(ctx, cmd) // Crop at Python traceback (NixOS read-only store error — results are unaffected) if idx := strings.Index(output, "Traceback (most recent call last)"); idx != -1 { output = strings.TrimSpace(output[:idx]) } count := 0 if err != nil && ctx.Err() != nil { out <- tools.Event{Tool: name, Type: tools.EventTypeError, Payload: "scan cancelled"} } else if output != "" { // Parse count from summary line: "returned N accounts" if m := accountsRe.FindStringSubmatch(output); len(m) == 2 { count, _ = strconv.Atoi(m[1]) } out <- tools.Event{Tool: name, Type: tools.EventTypeOutput, Payload: output} } out <- tools.Event{Tool: name, Type: tools.EventTypeCount, Payload: count} out <- tools.Event{Tool: name, Type: tools.EventTypeDone} return nil }