From f7fcfa623e670dc533bb378912829c73a3593e63 Mon Sep 17 00:00:00 2001 From: s Date: Mon, 3 Nov 2025 21:17:12 -0500 Subject: hi --- internal/client/admin.go | 124 ++++++++++++++++++++++++++++ internal/client/client.go | 119 +++++++++++++++++++++++++++ internal/client/client_test.go | 46 +++++++++++ internal/client/npd.go | 91 +++++++++++++++++++++ internal/client/osint.go | 32 ++++++++ internal/client/skiptrace.go | 179 +++++++++++++++++++++++++++++++++++++++++ internal/client/sl.go | 53 ++++++++++++ internal/client/usrsx.go | 72 +++++++++++++++++ internal/client/x.go | 23 ++++++ internal/config/config.go | 36 +++++++++ internal/config/errors.go | 7 ++ internal/models/admin.go | 43 ++++++++++ internal/models/npd.go | 42 ++++++++++ internal/models/osint.go | 29 +++++++ internal/models/skiptrace.go | 92 +++++++++++++++++++++ internal/models/sl.go | 23 ++++++ internal/models/usrsx.go | 33 ++++++++ internal/models/x.go | 22 +++++ internal/utils/output.go | 39 +++++++++ 19 files changed, 1105 insertions(+) create mode 100644 internal/client/admin.go create mode 100644 internal/client/client.go create mode 100644 internal/client/client_test.go create mode 100644 internal/client/npd.go create mode 100644 internal/client/osint.go create mode 100644 internal/client/skiptrace.go create mode 100644 internal/client/sl.go create mode 100644 internal/client/usrsx.go create mode 100644 internal/client/x.go create mode 100644 internal/config/config.go create mode 100644 internal/config/errors.go create mode 100644 internal/models/admin.go create mode 100644 internal/models/npd.go create mode 100644 internal/models/osint.go create mode 100644 internal/models/skiptrace.go create mode 100644 internal/models/sl.go create mode 100644 internal/models/usrsx.go create mode 100644 internal/models/x.go create mode 100644 internal/utils/output.go (limited to 'internal') diff --git a/internal/client/admin.go b/internal/client/admin.go new file mode 100644 index 0000000..a5a9519 --- /dev/null +++ b/internal/client/admin.go @@ -0,0 +1,124 @@ +package client + +import ( + "dborg/internal/models" + "encoding/json" + "fmt" + "net/url" +) + +func (c *Client) ListAccounts() (*models.AdminResponse, error) { + data, err := c.Get("/admin/accounts", nil) + if err != nil { + return nil, err + } + + var response models.AdminResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse admin response: %w", err) + } + + return &response, nil +} + +func (c *Client) CreateAccount(req *models.AccountCreateRequest) (*models.AdminResponse, error) { + data, err := c.Post("/admin/accounts", req) + if err != nil { + return nil, err + } + + var account models.Account + if err := json.Unmarshal(data, &account); err == nil && account.APIKey != "" { + return &models.AdminResponse{ + Success: true, + Account: &account, + }, nil + } + + var response models.AdminResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse admin response: %w", err) + } + + return &response, nil +} + +func (c *Client) DeleteAccount(apiKey string) (*models.AdminResponse, error) { + path := fmt.Sprintf("/admin/accounts/%s", url.PathEscape(apiKey)) + data, err := c.Delete(path) + if err != nil { + return nil, err + } + + var response models.AdminResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse admin response: %w", err) + } + + return &response, nil +} + +func (c *Client) UpdateCredits(apiKey string, credits int) (*models.AdminResponse, error) { + path := fmt.Sprintf("/admin/accounts/%s/credits", url.PathEscape(apiKey)) + req := &models.AddCreditsRequest{ + Credits: credits, + } + + data, err := c.Post(path, req) + if err != nil { + return nil, err + } + + var response models.AdminResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse admin response: %w", err) + } + + return &response, nil +} + +func (c *Client) SetCredits(apiKey string, credits int) (*models.AdminResponse, error) { + path := fmt.Sprintf("/admin/accounts/%s/credits", url.PathEscape(apiKey)) + req := &models.SetCreditsRequest{ + Credits: credits, + } + + data, err := c.Put(path, req) + if err != nil { + return nil, err + } + + var account models.Account + if err := json.Unmarshal(data, &account); err == nil && account.APIKey != "" { + return &models.AdminResponse{ + Success: true, + Account: &account, + }, nil + } + + var response models.AdminResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse admin response: %w", err) + } + + return &response, nil +} + +func (c *Client) ToggleAccount(apiKey string, enable bool) (*models.AdminResponse, error) { + path := fmt.Sprintf("/admin/accounts/%s/disable", url.PathEscape(apiKey)) + req := &models.DisableAccountRequest{ + Disabled: !enable, + } + + data, err := c.Patch(path, req) + if err != nil { + return nil, err + } + + var response models.AdminResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse admin response: %w", err) + } + + return &response, nil +} diff --git a/internal/client/client.go b/internal/client/client.go new file mode 100644 index 0000000..098479f --- /dev/null +++ b/internal/client/client.go @@ -0,0 +1,119 @@ +package client + +import ( + "bytes" + "dborg/internal/config" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "time" +) + +type Client struct { + config *config.Config + httpClient *http.Client +} + +func New(cfg *config.Config) (*Client, error) { + if err := cfg.Validate(); err != nil { + return nil, err + } + + return &Client{ + config: cfg, + httpClient: &http.Client{ + Timeout: cfg.Timeout, + }, + }, nil +} + +func (c *Client) doRequest(method, path string, params url.Values, body interface{}) ([]byte, error) { + fullURL := c.config.BaseURL + path + if params != nil && len(params) > 0 { + fullURL += "?" + params.Encode() + } + + var reqBody io.Reader + if body != nil { + jsonData, err := json.Marshal(body) + if err != nil { + return nil, fmt.Errorf("failed to marshal request body: %w", err) + } + reqBody = bytes.NewBuffer(jsonData) + } + + req, err := http.NewRequest(method, fullURL, reqBody) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("X-API-Key", c.config.APIKey) + req.Header.Set("User-Agent", c.config.UserAgent) + if body != nil { + req.Header.Set("Content-Type", "application/json") + } + + var resp *http.Response + var lastErr error + + for attempt := 0; attempt <= c.config.MaxRetries; attempt++ { + if attempt > 0 { + time.Sleep(time.Duration(attempt) * time.Second) + } + + resp, err = c.httpClient.Do(req) + if err != nil { + lastErr = err + continue + } + + defer resp.Body.Close() + + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusCreated { + return io.ReadAll(resp.Body) + } + + bodyBytes, _ := io.ReadAll(resp.Body) + + switch resp.StatusCode { + case http.StatusForbidden: + lastErr = fmt.Errorf("access denied (403): %s - This endpoint requires premium access", string(bodyBytes)) + case http.StatusUnauthorized: + lastErr = fmt.Errorf("unauthorized (401): %s - Check your API key", string(bodyBytes)) + case http.StatusTooManyRequests: + lastErr = fmt.Errorf("rate limit exceeded (429): %s", string(bodyBytes)) + case http.StatusBadRequest: + lastErr = fmt.Errorf("bad request (400): %s", string(bodyBytes)) + default: + lastErr = fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + } + + if resp.StatusCode != http.StatusTooManyRequests && resp.StatusCode < 500 { + break + } + } + + return nil, lastErr +} + +func (c *Client) Get(path string, params url.Values) ([]byte, error) { + return c.doRequest(http.MethodGet, path, params, nil) +} + +func (c *Client) Post(path string, body interface{}) ([]byte, error) { + return c.doRequest(http.MethodPost, path, nil, body) +} + +func (c *Client) Delete(path string) ([]byte, error) { + return c.doRequest(http.MethodDelete, path, nil, nil) +} + +func (c *Client) Patch(path string, body interface{}) ([]byte, error) { + return c.doRequest(http.MethodPatch, path, nil, body) +} + +func (c *Client) Put(path string, body interface{}) ([]byte, error) { + return c.doRequest(http.MethodPut, path, nil, body) +} diff --git a/internal/client/client_test.go b/internal/client/client_test.go new file mode 100644 index 0000000..9bf453d --- /dev/null +++ b/internal/client/client_test.go @@ -0,0 +1,46 @@ +package client + +import ( + "dborg/internal/config" + "testing" + "time" +) + +func TestNewClient(t *testing.T) { + tests := []struct { + name string + config *config.Config + wantErr bool + }{ + { + name: "valid config", + config: &config.Config{ + APIKey: "test-key", + BaseURL: "https://db.org.ai", + Timeout: 30 * time.Second, + MaxRetries: 3, + UserAgent: "test-agent", + }, + wantErr: false, + }, + { + name: "missing API key", + config: &config.Config{ + BaseURL: "https://db.org.ai", + Timeout: 30 * time.Second, + MaxRetries: 3, + UserAgent: "test-agent", + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := New(tt.config) + if (err != nil) != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/internal/client/npd.go b/internal/client/npd.go new file mode 100644 index 0000000..c63327b --- /dev/null +++ b/internal/client/npd.go @@ -0,0 +1,91 @@ +package client + +import ( + "dborg/internal/models" + "encoding/json" + "fmt" + "net/url" +) + +func (c *Client) SearchNPD(params *models.NPDParams) (*models.NPDResponse, error) { + queryParams := url.Values{} + + if params.ID != "" { + queryParams.Add("id", params.ID) + } + if params.FirstName != "" { + queryParams.Add("firstname", params.FirstName) + } + if params.LastName != "" { + queryParams.Add("lastname", params.LastName) + } + if params.MiddleName != "" { + queryParams.Add("middlename", params.MiddleName) + } + if params.DOB != "" { + queryParams.Add("dob", params.DOB) + } + if params.SSN != "" { + queryParams.Add("ssn", params.SSN) + } + if params.Phone1 != "" { + queryParams.Add("phone1", params.Phone1) + } + if params.Address != "" { + queryParams.Add("address", params.Address) + } + if params.City != "" { + queryParams.Add("city", params.City) + } + if params.State != "" { + queryParams.Add("st", params.State) + } + if params.Zip != "" { + queryParams.Add("zip", params.Zip) + } + if params.CountyName != "" { + queryParams.Add("county_name", params.CountyName) + } + if params.NameSuffix != "" { + queryParams.Add("name_suff", params.NameSuffix) + } + if params.AKA1FullName != "" { + queryParams.Add("aka1fullname", params.AKA1FullName) + } + if params.AKA2FullName != "" { + queryParams.Add("aka2fullname", params.AKA2FullName) + } + if params.AKA3FullName != "" { + queryParams.Add("aka3fullname", params.AKA3FullName) + } + if params.Alt1DOB != "" { + queryParams.Add("alt1dob", params.Alt1DOB) + } + if params.Alt2DOB != "" { + queryParams.Add("alt2dob", params.Alt2DOB) + } + if params.Alt3DOB != "" { + queryParams.Add("alt3dob", params.Alt3DOB) + } + if params.StartDate != "" { + queryParams.Add("startdat", params.StartDate) + } + if params.MaxHits > 0 && params.MaxHits != 10 { + queryParams.Add("max_hits", fmt.Sprintf("%d", params.MaxHits)) + } + if params.SortBy != "" { + queryParams.Add("sort_by", params.SortBy) + } + + data, err := c.Get("/npd/search", queryParams) + if err != nil { + return nil, err + } + + var response models.NPDResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse NPD response: %w", err) + } + + return &response, nil +} diff --git a/internal/client/osint.go b/internal/client/osint.go new file mode 100644 index 0000000..70dbb72 --- /dev/null +++ b/internal/client/osint.go @@ -0,0 +1,32 @@ +package client + +import ( + "dborg/internal/models" + "encoding/json" + "fmt" + "net/url" +) + +func (c *Client) LookupBSSID(params *models.BSSIDParams) (*models.BSSIDLookupResponse, error) { + path := fmt.Sprintf("/osint/bssid/%s", url.PathEscape(params.BSSID)) + + queryParams := url.Values{} + if params.All { + queryParams.Add("all", "true") + } + if params.Map { + queryParams.Add("map", "true") + } + + data, err := c.Get(path, queryParams) + if err != nil { + return nil, err + } + + var response models.BSSIDLookupResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse BSSID lookup response: %w", err) + } + + return &response, nil +} diff --git a/internal/client/skiptrace.go b/internal/client/skiptrace.go new file mode 100644 index 0000000..b1d5008 --- /dev/null +++ b/internal/client/skiptrace.go @@ -0,0 +1,179 @@ +package client + +import ( + "bufio" + "bytes" + "dborg/internal/models" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +func parseSSEResponse(data []byte) ([]byte, error) { + scanner := bufio.NewScanner(bytes.NewReader(data)) + + const maxScanTokenSize = 10 * 1024 * 1024 + buf := make([]byte, maxScanTokenSize) + scanner.Buffer(buf, maxScanTokenSize) + + var resultData []byte + var foundResult bool + + for scanner.Scan() { + line := scanner.Text() + + if line == "event: result" { + foundResult = true + continue + } + + if foundResult && strings.HasPrefix(line, "data: ") { + resultData = []byte(strings.TrimPrefix(line, "data: ")) + break + } + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading SSE response: %w", err) + } + + if resultData == nil { + return nil, fmt.Errorf("no result event found in SSE response") + } + + trimmed := strings.TrimSpace(string(resultData)) + if !strings.HasPrefix(trimmed, "{") && !strings.HasPrefix(trimmed, "[") { + return nil, fmt.Errorf("API returned: %s", trimmed) + } + + return resultData, nil +} + +func (c *Client) getSSE(path string, params url.Values) ([]byte, error) { + fullURL := c.config.BaseURL + path + if params != nil && len(params) > 0 { + fullURL += "?" + params.Encode() + } + + req, err := http.NewRequest("GET", fullURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("X-API-Key", c.config.APIKey) + req.Header.Set("User-Agent", c.config.UserAgent) + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := io.ReadAll(resp.Body) + switch resp.StatusCode { + case http.StatusForbidden: + return nil, fmt.Errorf("access denied (403): %s - This endpoint requires premium access", string(bodyBytes)) + case http.StatusUnauthorized: + return nil, fmt.Errorf("unauthorized (401): %s - Check your API key", string(bodyBytes)) + case http.StatusTooManyRequests: + return nil, fmt.Errorf("rate limit exceeded (429): %s", string(bodyBytes)) + case http.StatusBadRequest: + return nil, fmt.Errorf("bad request (400): %s", string(bodyBytes)) + default: + return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + } + } + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response: %w", err) + } + + contentType := resp.Header.Get("Content-Type") + + if strings.HasPrefix(string(data), "event:") || strings.Contains(contentType, "text/event-stream") { + return parseSSEResponse(data) + } + + return data, nil +} + +func (c *Client) SearchPeople(params *models.SkiptraceParams) (*models.SkiptraceResponse, error) { + queryParams := url.Values{} + queryParams.Set("first_name", params.FirstName) + queryParams.Set("last_name", params.LastName) + + if params.City != "" { + queryParams.Set("city", params.City) + } + if params.State != "" { + queryParams.Set("state", params.State) + } + if params.Age != "" { + queryParams.Set("age", params.Age) + } + + data, err := c.getSSE("/prem/skiptrace/people/search", queryParams) + if err != nil { + return nil, err + } + + var response models.SkiptraceResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &response, nil +} + +func (c *Client) GetPersonReport(sxKey string, selection int) (*models.SkiptraceReportResponse, error) { + path := fmt.Sprintf("/prem/skiptrace/people/report/%s/%d", sxKey, selection) + + data, err := c.getSSE(path, nil) + if err != nil { + return nil, err + } + + var response models.SkiptraceReportResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &response, nil +} + +func (c *Client) SearchPhone(phone string) (*models.SkiptracePhoneResponse, error) { + path := fmt.Sprintf("/prem/skiptrace/phone/%s", phone) + + data, err := c.getSSE(path, nil) + if err != nil { + return nil, err + } + + var response models.SkiptracePhoneResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &response, nil +} + +func (c *Client) SearchEmail(email string) (*models.SkiptraceEmailResponse, error) { + path := fmt.Sprintf("/prem/skiptrace/email/%s", email) + + data, err := c.getSSE(path, nil) + if err != nil { + return nil, err + } + + var response models.SkiptraceEmailResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse response: %w", err) + } + + return &response, nil +} diff --git a/internal/client/sl.go b/internal/client/sl.go new file mode 100644 index 0000000..fb3b270 --- /dev/null +++ b/internal/client/sl.go @@ -0,0 +1,53 @@ +package client + +import ( + "dborg/internal/models" + "encoding/json" + "fmt" + "net/url" +) + +func (c *Client) SearchStealerLogs(params *models.SLParams) (*models.SLResponse, error) { + queryParams := url.Values{} + queryParams.Add("query", params.Query) + + if params.MaxHits > 0 && params.MaxHits != 10 { + queryParams.Add("max_hits", fmt.Sprintf("%d", params.MaxHits)) + } + if params.SortBy != "" { + queryParams.Add("sort_by", params.SortBy) + } + if params.IngestStartDate != "" { + queryParams.Add("ingest_start_date", params.IngestStartDate) + } + if params.IngestEndDate != "" { + queryParams.Add("ingest_end_date", params.IngestEndDate) + } + if params.PostedStartDate != "" { + queryParams.Add("posted_start_date", params.PostedStartDate) + } + if params.PostedEndDate != "" { + queryParams.Add("posted_end_date", params.PostedEndDate) + } + if params.Format != "" && params.Format != "json" { + queryParams.Add("format", params.Format) + } + + data, err := c.Get("/sl/search", queryParams) + if err != nil { + return nil, err + } + + if params.Format != "" && params.Format != "json" { + return &models.SLResponse{ + Message: string(data), + }, nil + } + + var response models.SLResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse stealer logs response: %w", err) + } + + return &response, nil +} diff --git a/internal/client/usrsx.go b/internal/client/usrsx.go new file mode 100644 index 0000000..456acbf --- /dev/null +++ b/internal/client/usrsx.go @@ -0,0 +1,72 @@ +package client + +import ( + "bufio" + "dborg/internal/models" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +func (c *Client) CheckUsernameStream(params *models.USRSXParams, callback func(result json.RawMessage) error) error { + queryParams := url.Values{} + + if len(params.Sites) > 0 { + queryParams.Add("sites", strings.Join(params.Sites, ",")) + } + if params.Fuzzy { + queryParams.Add("fuzzy", "true") + } + if params.MaxTasks > 0 && params.MaxTasks != 50 { + queryParams.Add("max_tasks", fmt.Sprintf("%d", params.MaxTasks)) + } + + path := fmt.Sprintf("/osint/username/%s", url.PathEscape(params.Username)) + fullURL := c.config.BaseURL + path + if len(queryParams) > 0 { + fullURL += "?" + queryParams.Encode() + } + + req, err := http.NewRequest(http.MethodGet, fullURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("X-API-Key", c.config.APIKey) + req.Header.Set("User-Agent", c.config.UserAgent) + req.Header.Set("Accept", "application/x-ndjson, application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to execute request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(body)) + } + + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + line := scanner.Bytes() + if len(line) == 0 { + continue + } + + if err := callback(json.RawMessage(line)); err != nil { + return err + } + } + + if err := scanner.Err(); err != nil { + if !strings.Contains(err.Error(), "context deadline exceeded") && !strings.Contains(err.Error(), "timeout") { + return fmt.Errorf("stream reading error: %w", err) + } + } + + return nil +} diff --git a/internal/client/x.go b/internal/client/x.go new file mode 100644 index 0000000..8bdb21c --- /dev/null +++ b/internal/client/x.go @@ -0,0 +1,23 @@ +package client + +import ( + "dborg/internal/models" + "encoding/json" + "fmt" + "net/url" +) + +func (c *Client) SearchTwitterHistory(username string) (*models.XResponse, error) { + path := fmt.Sprintf("/x/search/%s", url.PathEscape(username)) + data, err := c.Get(path, nil) + if err != nil { + return nil, err + } + + var response models.XResponse + if err := json.Unmarshal(data, &response); err != nil { + return nil, fmt.Errorf("failed to parse Twitter/X response: %w", err) + } + + return &response, nil +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..44ca7e6 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,36 @@ +package config + +import ( + "os" + "time" +) + +type Config struct { + APIKey string + BaseURL string + Timeout time.Duration + MaxRetries int + UserAgent string +} + +func New() *Config { + return &Config{ + APIKey: os.Getenv("DBORG_API_KEY"), + BaseURL: "https://db.org.ai", + Timeout: 30 * time.Second, + MaxRetries: 3, + UserAgent: "dborg-cli/1.0", + } +} + +func (c *Config) WithAPIKey(key string) *Config { + c.APIKey = key + return c +} + +func (c *Config) Validate() error { + if c.APIKey == "" { + return ErrMissingAPIKey + } + return nil +} diff --git a/internal/config/errors.go b/internal/config/errors.go new file mode 100644 index 0000000..4fd3636 --- /dev/null +++ b/internal/config/errors.go @@ -0,0 +1,7 @@ +package config + +import "errors" + +var ( + ErrMissingAPIKey = errors.New("API key required: set DBORG_API_KEY environment variable or use --api-key flag") +) diff --git a/internal/models/admin.go b/internal/models/admin.go new file mode 100644 index 0000000..5cf0f37 --- /dev/null +++ b/internal/models/admin.go @@ -0,0 +1,43 @@ +package models + +type Account struct { + APIKey string `json:"api_key"` + Name string `json:"name"` + Credits int `json:"credits"` + Unlimited bool `json:"unlimited"` + Disabled bool `json:"disabled"` + IsPremium bool `json:"is_premium"` + CreatedAt interface{} `json:"created_at,omitempty"` +} + +type AccountCreateRequest struct { + Name string `json:"name"` + Credits int `json:"credits,omitempty"` + Unlimited bool `json:"unlimited,omitempty"` + IsPremium bool `json:"is_premium,omitempty"` +} + +type AccountUpdateRequest struct { + Credits int `json:"credits,omitempty"` + Disabled bool `json:"disabled"` +} + +type AddCreditsRequest struct { + Credits int `json:"credits"` +} + +type SetCreditsRequest struct { + Credits int `json:"credits"` +} + +type DisableAccountRequest struct { + Disabled bool `json:"disabled"` +} + +type AdminResponse struct { + Success bool `json:"success,omitempty"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` + Account *Account `json:"account,omitempty"` + Accounts []Account `json:"accounts,omitempty"` +} diff --git a/internal/models/npd.go b/internal/models/npd.go new file mode 100644 index 0000000..a1da05e --- /dev/null +++ b/internal/models/npd.go @@ -0,0 +1,42 @@ +package models + +type NPDParams struct { + ID string `json:"id,omitempty"` + FirstName string `json:"firstname,omitempty"` + LastName string `json:"lastname,omitempty"` + MiddleName string `json:"middlename,omitempty"` + DOB string `json:"dob,omitempty"` + SSN string `json:"ssn,omitempty"` + Phone1 string `json:"phone1,omitempty"` + Address string `json:"address,omitempty"` + City string `json:"city,omitempty"` + State string `json:"st,omitempty"` + Zip string `json:"zip,omitempty"` + CountyName string `json:"county_name,omitempty"` + NameSuffix string `json:"name_suff,omitempty"` + AKA1FullName string `json:"aka1fullname,omitempty"` + AKA2FullName string `json:"aka2fullname,omitempty"` + AKA3FullName string `json:"aka3fullname,omitempty"` + Alt1DOB string `json:"alt1dob,omitempty"` + Alt2DOB string `json:"alt2dob,omitempty"` + Alt3DOB string `json:"alt3dob,omitempty"` + StartDate string `json:"startdat,omitempty"` + MaxHits int `json:"max_hits,omitempty"` + SortBy string `json:"sort_by,omitempty"` +} + +type NPDResponse struct { + MaxHits int `json:"max_hits"` + Results struct { + ElapsedTimeMicros int `json:"elapsed_time_micros"` + Errors []string `json:"errors"` + Hits []map[string]any `json:"hits"` + NumHits int `json:"num_hits"` + } `json:"results"` + Credits struct { + Remaining int `json:"remaining"` + Unlimited bool `json:"unlimited"` + } `json:"credits"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} diff --git a/internal/models/osint.go b/internal/models/osint.go new file mode 100644 index 0000000..7170c27 --- /dev/null +++ b/internal/models/osint.go @@ -0,0 +1,29 @@ +package models + +type BSSIDParams struct { + BSSID string + All bool + Map bool +} + +type LocationInfo struct { + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` + Accuracy int `json:"accuracy"` +} + +type BSSIDResult struct { + BSSID string `json:"bssid"` + Location *LocationInfo `json:"location"` + MapURL string `json:"map_url,omitempty"` +} + +type BSSIDLookupResponse struct { + BSSID string `json:"bssid"` + Results []BSSIDResult `json:"results"` + MapURL string `json:"map_url,omitempty"` +} + +type ErrorResponse struct { + Error string `json:"error"` +} diff --git a/internal/models/skiptrace.go b/internal/models/skiptrace.go new file mode 100644 index 0000000..c87fe72 --- /dev/null +++ b/internal/models/skiptrace.go @@ -0,0 +1,92 @@ +package models + +import "encoding/json" + +type SkiptraceParams struct { + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + City string `json:"city,omitempty"` + State string `json:"state,omitempty"` + Age string `json:"age,omitempty"` +} + +type SkiptraceResponse struct { + Data map[string]interface{} `json:"-"` + SXKey string `json:"sx_key,omitempty"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +func (s *SkiptraceResponse) UnmarshalJSON(data []byte) error { + type Alias SkiptraceResponse + aux := &struct { + *Alias + }{ + Alias: (*Alias)(s), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + s.Data = make(map[string]interface{}) + return json.Unmarshal(data, &s.Data) +} + +type SkiptraceReportResponse struct { + Data map[string]interface{} `json:"-"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +func (s *SkiptraceReportResponse) UnmarshalJSON(data []byte) error { + type Alias SkiptraceReportResponse + aux := &struct { + *Alias + }{ + Alias: (*Alias)(s), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + s.Data = make(map[string]interface{}) + return json.Unmarshal(data, &s.Data) +} + +type SkiptracePhoneResponse struct { + Data map[string]interface{} `json:"-"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +func (s *SkiptracePhoneResponse) UnmarshalJSON(data []byte) error { + type Alias SkiptracePhoneResponse + aux := &struct { + *Alias + }{ + Alias: (*Alias)(s), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + s.Data = make(map[string]interface{}) + return json.Unmarshal(data, &s.Data) +} + +type SkiptraceEmailResponse struct { + Data map[string]interface{} `json:"-"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +func (s *SkiptraceEmailResponse) UnmarshalJSON(data []byte) error { + type Alias SkiptraceEmailResponse + aux := &struct { + *Alias + }{ + Alias: (*Alias)(s), + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + s.Data = make(map[string]interface{}) + return json.Unmarshal(data, &s.Data) +} diff --git a/internal/models/sl.go b/internal/models/sl.go new file mode 100644 index 0000000..d55279f --- /dev/null +++ b/internal/models/sl.go @@ -0,0 +1,23 @@ +package models + +type SLParams struct { + Query string `json:"query"` + MaxHits int `json:"max_hits,omitempty"` + SortBy string `json:"sort_by,omitempty"` + IngestStartDate string `json:"ingest_start_date,omitempty"` + IngestEndDate string `json:"ingest_end_date,omitempty"` + PostedStartDate string `json:"posted_start_date,omitempty"` + PostedEndDate string `json:"posted_end_date,omitempty"` + Format string `json:"format,omitempty"` +} + +type SLResponse struct { + MaxHits int `json:"max_hits"` + Results interface{} `json:"results"` + Credits struct { + Remaining int `json:"remaining"` + Unlimited bool `json:"unlimited"` + } `json:"credits"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} diff --git a/internal/models/usrsx.go b/internal/models/usrsx.go new file mode 100644 index 0000000..f9264be --- /dev/null +++ b/internal/models/usrsx.go @@ -0,0 +1,33 @@ +package models + +type USRSXParams struct { + Username string `json:"username"` + Sites []string `json:"sites,omitempty"` + Fuzzy bool `json:"fuzzy,omitempty"` + MaxTasks int `json:"max_tasks,omitempty"` +} + +type USRSXResponse struct { + Username string `json:"username"` + Results []SiteResult `json:"results"` + Credits struct { + Remaining int `json:"remaining"` + Unlimited bool `json:"unlimited"` + } `json:"credits"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +type SiteResult struct { + SiteName string `json:"site_name"` + Username string `json:"username"` + URL string `json:"url"` + Status string `json:"status"` + ResponseCode int `json:"response_code"` + Category string `json:"category,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + Type string `json:"type,omitempty"` + Elapsed float64 `json:"elapsed,omitempty"` + Error string `json:"error,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} diff --git a/internal/models/x.go b/internal/models/x.go new file mode 100644 index 0000000..f8c7a70 --- /dev/null +++ b/internal/models/x.go @@ -0,0 +1,22 @@ +package models + +type XResponse struct { + Username string `json:"username,omitempty"` + PreviousUsernames []UserHistory `json:"previous_usernames,omitempty"` + + Query string `json:"query,omitempty"` + Response string `json:"response,omitempty"` + Data interface{} `json:"data,omitempty"` + + Credits struct { + Remaining int `json:"remaining"` + Unlimited bool `json:"unlimited"` + } `json:"credits"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` +} + +type UserHistory struct { + Username string `json:"username"` + TimeAgo string `json:"time_ago"` +} diff --git a/internal/utils/output.go b/internal/utils/output.go new file mode 100644 index 0000000..3f2347c --- /dev/null +++ b/internal/utils/output.go @@ -0,0 +1,39 @@ +package utils + +import ( + "encoding/json" + "fmt" + "os" + "text/tabwriter" +) + +func PrintJSON(data any) error { + output, err := json.MarshalIndent(data, "", " ") + if err != nil { + return fmt.Errorf("failed to format JSON: %w", err) + } + fmt.Println(string(output)) + return nil +} + +func PrintTable(headers []string, rows [][]string) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer w.Flush() + + for _, h := range headers { + fmt.Fprintf(w, "%s\t", h) + } + fmt.Fprintln(w) + + for _, row := range rows { + for _, col := range row { + fmt.Fprintf(w, "%s\t", col) + } + fmt.Fprintln(w) + } +} + +func PrintError(err error) { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) +} -- cgit v1.2.3