summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authors <[email protected]>2025-11-08 02:44:13 -0500
committers <[email protected]>2025-11-08 02:44:13 -0500
commitdfcf52f30cdbde3a4e1400024b0c27451d179e5d (patch)
treecdcac72f0d58b0689777644c771e80d53b502434
parent486a369f05125a3b86d663ea94684466e0658099 (diff)
downloaddborg-dfcf52f30cdbde3a4e1400024b0c27451d179e5d.tar.gz
dborg-dfcf52f30cdbde3a4e1400024b0c27451d179e5d.zip
feat: add unauthenticated client support and new osint/x commands
-rw-r--r--cmd/dns.go2
-rw-r--r--cmd/osint.go45
-rw-r--r--cmd/root.go2
-rw-r--r--cmd/x.go54
-rw-r--r--internal/client/client.go9
-rw-r--r--internal/client/client_test.go42
-rw-r--r--internal/client/osint.go33
-rw-r--r--internal/client/x.go30
-rw-r--r--internal/models/osint.go11
-rw-r--r--internal/models/x.go30
10 files changed, 252 insertions, 6 deletions
diff --git a/cmd/dns.go b/cmd/dns.go
index 022d2c9..d6776ee 100644
--- a/cmd/dns.go
+++ b/cmd/dns.go
@@ -29,7 +29,7 @@ func runDNSTLDCheck(cmd *cobra.Command, args []string) error {
showOnly, _ := cmd.Flags().GetString("show-only")
cfg := config.New()
- c, err := client.New(cfg)
+ c, err := client.NewUnauthenticated(cfg)
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
diff --git a/cmd/osint.go b/cmd/osint.go
index 5aa8799..6b97464 100644
--- a/cmd/osint.go
+++ b/cmd/osint.go
@@ -41,11 +41,20 @@ var osintBreachForumCmd = &cobra.Command{
RunE: runOsintBreachForumSearch,
}
+var osintFilesCmd = &cobra.Command{
+ Use: "files [url]",
+ Short: "Search open directory files",
+ Long: `Search for files in open directories using various filters (free OSINT endpoint)`,
+ Args: cobra.ExactArgs(1),
+ RunE: runOsintFilesSearch,
+}
+
func init() {
rootCmd.AddCommand(osintCmd)
osintCmd.AddCommand(osintUsernameCmd)
osintCmd.AddCommand(osintBSSIDCmd)
osintCmd.AddCommand(osintBreachForumCmd)
+ osintCmd.AddCommand(osintFilesCmd)
osintUsernameCmd.Flags().StringSliceP("sites", "s", []string{}, "Specific sites to check (comma-separated)")
osintUsernameCmd.Flags().BoolP("fuzzy", "f", false, "Enable fuzzy validation mode")
@@ -56,13 +65,18 @@ func init() {
osintBSSIDCmd.Flags().BoolP("osm", "o", false, "Include OpenStreetMap URL for the location")
osintBreachForumCmd.Flags().IntP("max_hits", "m", 10, "Maximum number of hits to return")
+
+ osintFilesCmd.Flags().StringP("filename", "n", "", "Search term to match in filenames")
+ osintFilesCmd.Flags().StringP("extension", "e", "", "Filter by file extension(s) - comma-separated (e.g., pdf,doc,txt)")
+ osintFilesCmd.Flags().StringP("exclude", "x", "html,HTML", "Exclude file extension(s) - comma-separated")
+ osintFilesCmd.Flags().IntP("size", "s", 10, "Number of results to return (max 40)")
+ osintFilesCmd.Flags().IntP("from", "f", 0, "Starting offset for pagination")
}
func runOsintUsernameCheck(cmd *cobra.Command, args []string) error {
- apiKey, _ := cmd.Flags().GetString("api-key")
- cfg := config.New().WithAPIKey(apiKey)
+ cfg := config.New()
- c, err := client.New(cfg)
+ c, err := client.NewUnauthenticated(cfg)
if err != nil {
return err
}
@@ -131,3 +145,28 @@ func runOsintBreachForumSearch(cmd *cobra.Command, args []string) error {
return utils.PrintJSON(response)
}
+
+func runOsintFilesSearch(cmd *cobra.Command, args []string) error {
+ cfg := config.New()
+
+ c, err := client.NewUnauthenticated(cfg)
+ if err != nil {
+ return err
+ }
+
+ params := &models.OpenDirectorySearchParams{
+ URL: args[0],
+ }
+ params.Filename, _ = cmd.Flags().GetString("filename")
+ params.Extension, _ = cmd.Flags().GetString("extension")
+ params.Exclude, _ = cmd.Flags().GetString("exclude")
+ params.Size, _ = cmd.Flags().GetInt("size")
+ params.From, _ = cmd.Flags().GetInt("from")
+
+ response, err := c.SearchOpenDirectoryFiles(params)
+ if err != nil {
+ return err
+ }
+
+ return utils.PrintJSON(response)
+}
diff --git a/cmd/root.go b/cmd/root.go
index cb6a676..e5939ca 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -12,7 +12,7 @@ var rootCmd = &cobra.Command{
Use: "dborg",
Short: "DB.org.ai CLI client",
Long: `█▀▀▄ █▀▀▄ █▀▀█ █▀▀█ █▀▀▀ █▀▀█ ▀█▀
-█░░█ █▀▀▄░░░█░░█ █▄▄▀ █░▀█░░░█▄▄█░░█░░
+█░░█░█▀▀▄░░░█░░█░█▄▄▀░█░▀█░░░█▄▄█░░█
▀▀▀▀ ▀▀▀▀ ▀ ▀▀▀▀ ▀ ▀ ▀▀▀▀ ▀ ▀ ▀ ▀▀▀
DB.org.ai CLI client`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
diff --git a/cmd/x.go b/cmd/x.go
index c88243b..d0d3209 100644
--- a/cmd/x.go
+++ b/cmd/x.go
@@ -32,10 +32,28 @@ var xTweetsCmd = &cobra.Command{
RunE: runXTweetsSearch,
}
+var xFirstCmd = &cobra.Command{
+ Use: "first [username]",
+ Short: "Get first 20 followers of a Twitter/X account",
+ Long: `Retrieves the first 20 followers of a Twitter/X account`,
+ Args: cobra.ExactArgs(1),
+ RunE: runXFirstFollowers,
+}
+
+var xNFLCmd = &cobra.Command{
+ Use: "nfl [username]",
+ Short: "Get notable followers of a Twitter/X account",
+ Long: `Retrieves the notable followers (influential accounts) following a Twitter/X account`,
+ Args: cobra.ExactArgs(1),
+ RunE: runXNotableFollowers,
+}
+
func init() {
rootCmd.AddCommand(xCmd)
xCmd.AddCommand(xHistoryCmd)
xCmd.AddCommand(xTweetsCmd)
+ xCmd.AddCommand(xFirstCmd)
+ xCmd.AddCommand(xNFLCmd)
}
func runXHistorySearch(cmd *cobra.Command, args []string) error {
@@ -72,7 +90,7 @@ func runXHistorySearch(cmd *cobra.Command, args []string) error {
func runXTweetsSearch(cmd *cobra.Command, args []string) error {
cfg := config.New()
- c, err := client.New(cfg)
+ c, err := client.NewUnauthenticated(cfg)
if err != nil {
return err
}
@@ -88,3 +106,37 @@ func runXTweetsSearch(cmd *cobra.Command, args []string) error {
return nil
}
+
+func runXFirstFollowers(cmd *cobra.Command, args []string) error {
+ apiKey, _ := cmd.Flags().GetString("api-key")
+ cfg := config.New().WithAPIKey(apiKey)
+
+ c, err := client.New(cfg)
+ if err != nil {
+ return err
+ }
+
+ response, err := c.GetFirstFollowers(args[0])
+ if err != nil {
+ return err
+ }
+
+ return utils.PrintJSON(response)
+}
+
+func runXNotableFollowers(cmd *cobra.Command, args []string) error {
+ apiKey, _ := cmd.Flags().GetString("api-key")
+ cfg := config.New().WithAPIKey(apiKey)
+
+ c, err := client.New(cfg)
+ if err != nil {
+ return err
+ }
+
+ response, err := c.GetNotableFollowers(args[0])
+ if err != nil {
+ return err
+ }
+
+ return utils.PrintJSON(response)
+}
diff --git a/internal/client/client.go b/internal/client/client.go
index 468fae9..eaf87cb 100644
--- a/internal/client/client.go
+++ b/internal/client/client.go
@@ -29,6 +29,15 @@ func New(cfg *config.Config) (*Client, error) {
}, nil
}
+func NewUnauthenticated(cfg *config.Config) (*Client, error) {
+ 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 {
diff --git a/internal/client/client_test.go b/internal/client/client_test.go
index 31939fb..fb21755 100644
--- a/internal/client/client_test.go
+++ b/internal/client/client_test.go
@@ -44,3 +44,45 @@ func TestNewClient(t *testing.T) {
})
}
}
+
+func TestNewUnauthenticatedClient(t *testing.T) {
+ tests := []struct {
+ name string
+ config *config.Config
+ wantErr bool
+ }{
+ {
+ name: "config with API key",
+ config: &config.Config{
+ APIKey: "test-key",
+ BaseURL: "https://db.org.ai",
+ Timeout: 30 * time.Second,
+ MaxRetries: 3,
+ UserAgent: "test-agent",
+ },
+ wantErr: false,
+ },
+ {
+ name: "config without API key",
+ config: &config.Config{
+ BaseURL: "https://db.org.ai",
+ Timeout: 30 * time.Second,
+ MaxRetries: 3,
+ UserAgent: "test-agent",
+ },
+ wantErr: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ c, err := NewUnauthenticated(tt.config)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("NewUnauthenticated() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ if c == nil && !tt.wantErr {
+ t.Error("NewUnauthenticated() returned nil client")
+ }
+ })
+ }
+}
diff --git a/internal/client/osint.go b/internal/client/osint.go
index 20f53ce..95e3550 100644
--- a/internal/client/osint.go
+++ b/internal/client/osint.go
@@ -55,3 +55,36 @@ func (c *Client) SearchBreachForum(params *models.BreachForumSearchParams) (*mod
return &response, nil
}
+
+func (c *Client) SearchOpenDirectoryFiles(params *models.OpenDirectorySearchParams) (*models.OpenDirectorySearchResponse, error) {
+ path := fmt.Sprintf("/osint/files/%s", url.PathEscape(params.URL))
+
+ queryParams := url.Values{}
+ if params.Filename != "" {
+ queryParams.Add("filename", params.Filename)
+ }
+ if params.Extension != "" {
+ queryParams.Add("extension", params.Extension)
+ }
+ if params.Exclude != "" {
+ queryParams.Add("exclude", params.Exclude)
+ }
+ if params.Size > 0 {
+ queryParams.Add("size", fmt.Sprintf("%d", params.Size))
+ }
+ if params.From > 0 {
+ queryParams.Add("from", fmt.Sprintf("%d", params.From))
+ }
+
+ data, err := c.Get(path, queryParams)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.OpenDirectorySearchResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse open directory search response: %w", err)
+ }
+
+ return &response, nil
+}
diff --git a/internal/client/x.go b/internal/client/x.go
index 6c899ad..bd16692 100644
--- a/internal/client/x.go
+++ b/internal/client/x.go
@@ -26,6 +26,36 @@ func (c *Client) SearchTwitterHistory(username string) (*models.XResponse, error
return &response, nil
}
+func (c *Client) GetFirstFollowers(username string) (*models.FirstFollowersResponse, error) {
+ path := fmt.Sprintf("/x/first/%s", url.PathEscape(username))
+ data, err := c.Get(path, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.FirstFollowersResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse first followers response: %w", err)
+ }
+
+ return &response, nil
+}
+
+func (c *Client) GetNotableFollowers(username string) (*models.NotableFollowersResponse, error) {
+ path := fmt.Sprintf("/x/nfl/%s", url.PathEscape(username))
+ data, err := c.Get(path, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ var response models.NotableFollowersResponse
+ if err := json.Unmarshal(data, &response); err != nil {
+ return nil, fmt.Errorf("failed to parse notable followers response: %w", err)
+ }
+
+ return &response, nil
+}
+
func (c *Client) FetchTweetsStream(username string, callback func(result json.RawMessage) error) error {
path := fmt.Sprintf("/x/tweets/%s", url.PathEscape(username))
fullURL := c.config.BaseURL + path
diff --git a/internal/models/osint.go b/internal/models/osint.go
index 6096df8..9f714c4 100644
--- a/internal/models/osint.go
+++ b/internal/models/osint.go
@@ -36,3 +36,14 @@ type BreachForumSearchResponse struct {
MaxHits int `json:"max_hits"`
Results interface{} `json:"results"`
}
+
+type OpenDirectorySearchParams struct {
+ URL string
+ Filename string
+ Extension string
+ Exclude string
+ Size int
+ From int
+}
+
+type OpenDirectorySearchResponse map[string]interface{}
diff --git a/internal/models/x.go b/internal/models/x.go
index b4e6eac..07c3117 100644
--- a/internal/models/x.go
+++ b/internal/models/x.go
@@ -49,3 +49,33 @@ type TweetsStreamResponse struct {
Complete *Complete `json:"complete,omitempty"`
Error string `json:"error,omitempty"`
}
+
+type FirstFollower struct {
+ Number int `json:"number"`
+ Username string `json:"username"`
+ Name string `json:"name"`
+}
+
+type FirstFollowersResponse struct {
+ Username string `json:"username"`
+ Followers []FirstFollower `json:"followers"`
+ Credits struct {
+ Remaining int `json:"remaining"`
+ Unlimited bool `json:"unlimited"`
+ } `json:"credits"`
+}
+
+type NotableFollower struct {
+ Username string `json:"username"`
+ FollowerCount string `json:"follower_count"`
+ Score float64 `json:"score"`
+}
+
+type NotableFollowersResponse struct {
+ Username string `json:"username"`
+ Followers []NotableFollower `json:"followers"`
+ Credits struct {
+ Remaining int `json:"remaining"`
+ Unlimited bool `json:"unlimited"`
+ } `json:"credits"`
+}