diff --git a/.img/easy_time_parsing.png b/.img/easy_time_parsing.png new file mode 100644 index 0000000..bc9a8ef Binary files /dev/null and b/.img/easy_time_parsing.png differ diff --git a/.img/easy_time_query_2.png b/.img/easy_time_query_2.png new file mode 100644 index 0000000..aee2744 Binary files /dev/null and b/.img/easy_time_query_2.png differ diff --git a/README.md b/README.md index 391ff27..56a0129 100644 --- a/README.md +++ b/README.md @@ -271,11 +271,29 @@ The logs can be easily queried from the Dehasher CLI. dehasher logs -l 10 # Show logs from the last 24 hours -dehasher logs -s "24 hours ago" +dehasher logs -s "last 24 hours" # Show logs from the last 24 hours with a severity of error or fatal -dehasher logs -s "24 hours ago" -v error,fatal +dehasher logs -s "05-01-2025" -v error,fatal ``` + +### Logs Dates +#### Dehasher utilized 'easy time' to determine the appropriate time for a given query. +![Alt text](.img/easy_time_parsing.png "Easy Time") +#### You may also used dates mixed with easy time to perform queries. +![Alt text](.img/easy_time_query_2.png "Mixed Time") +#### The following formats are supported: +- `last 24 hours` +- `last 2 days` +- `30 minutes ago` +- `45 seconds ago` +- `1 week ago` +- `05-01-2025` +- `05/01/2025` +- `05/01/25` +- `05-01-25` +- `May 01, 2025` + ## 🎉 Sample Run ```bash ar1ste1a@kali:~$ dehasher api -D .com -o -f json diff --git a/cmd/api.go b/cmd/api.go index 2a7bbaa..d4e0939 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -2,10 +2,12 @@ package cmd import ( "dehasher/internal/badger" + "dehasher/internal/debug" "dehasher/internal/dehashed" "dehasher/internal/sqlite" "fmt" "github.com/spf13/cobra" + "go.uber.org/zap" ) func init() { @@ -116,7 +118,18 @@ var ( dehasher.Start() fmt.Println("\n[*] Completing Process") - sqlite.StoreQueryOptions(queryOptions) + err := sqlite.StoreQueryOptions(queryOptions) + if err != nil { + if debugGlobal { + debug.PrintInfo("failed to store query options") + debug.PrintError(err) + } + zap.L().Error("store_query_options", + zap.String("message", "failed to store query options"), + zap.Error(err), + ) + fmt.Printf("Error storing query options: %v\n", err) + } }, } ) diff --git a/cmd/logs.go b/cmd/logs.go index 118a3f3..6f5b509 100644 --- a/cmd/logs.go +++ b/cmd/logs.go @@ -1,6 +1,7 @@ package cmd import ( + "dehasher/internal/easyTime" "dehasher/internal/pretty" "encoding/json" "fmt" @@ -73,6 +74,11 @@ var ( allLogs = append(allLogs, filepath.Join(logsPath, "info.log"), filepath.Join(logsPath, "error.log")) } + var timeChunk easyTime.TimeChunk + if logStartDate != "" { + timeChunk = easyTime.NewTimeChunk(logStartDate, logEndDate, debugGlobal) + } + var parsedLogs []LogEntry for _, logFile := range allLogs { // Read the log file @@ -97,7 +103,7 @@ var ( continue } - // Also unmarshal to get additional fields + // Unmarshal to get additional fields if err := json.Unmarshal([]byte(line), &rawEntry); err != nil { fmt.Printf("Error parsing raw log entry: %v\n", err) continue @@ -106,10 +112,10 @@ var ( // Parse the timestamp parsedTime, err := time.Parse("2006-01-02T15:04:05.999-0700", entry.Timestamp) if err != nil { - // Try alternative formats + // Try RFC3339 parsedTime, err = time.Parse(time.RFC3339, entry.Timestamp) if err != nil { - // Try another format + // Try RFC3339Nano parsedTime, err = time.Parse(time.RFC3339Nano, entry.Timestamp) if err != nil { fmt.Printf("Error parsing timestamp '%s': %v\n", entry.Timestamp, err) @@ -133,22 +139,8 @@ var ( (logFatal && strings.EqualFold(entry.Level, "FATAL")) { // Filter by date range if specified - if logStartDate != "" { - startDate, err := time.Parse("2006-01-02", logStartDate) - if err != nil { - fmt.Printf("Error parsing start date: %v\n", err) - } else if entry.ParsedTime.Before(startDate) { - continue - } - } - - if logEndDate != "" { - endDate, err := time.Parse("2006-01-02", logEndDate) - // Add one day to include the end date - endDate = endDate.Add(24 * time.Hour) - if err != nil { - fmt.Printf("Error parsing end date: %v\n", err) - } else if entry.ParsedTime.After(endDate) { + if timeChunk.IsSet() { + if entry.ParsedTime.Before(timeChunk.StartTime) || entry.ParsedTime.After(timeChunk.EndTime) { continue } } @@ -211,16 +203,3 @@ const ( FATAL UNKNOWN Severity = -1 ) - -func getSeverity(logLevel string) Severity { - switch logLevel { - case "INFO": - return INFO - case "ERROR": - return ERROR - case "FATAL": - return FATAL - default: - return UNKNOWN - } -} diff --git a/cmd/root.go b/cmd/root.go index e6f699a..706b259 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,7 @@ var ( // rootCmd is the base command for the CLI. rootCmd = &cobra.Command{ Use: "dehasher", - Short: `Dehasher is a cli tool for querying query.`, + Short: `Dehasher is a cli tool for querying the dehashed api.`, Long: fmt.Sprintf( "%s\n%s", ` @@ -42,7 +42,7 @@ var ( ––•–√\/––√\/––•––––•–√\/––√\/––•––––•–√\/––√\/––•––√\/––•––––•–√\/––√\/––•–– `, ), - Version: "v1.0", + Version: "v1.2.1", } ) @@ -63,7 +63,7 @@ func init() { rootCmd.CompletionOptions.HiddenDefaultCmd = true // Add global flags - rootCmd.PersistentFlags().BoolVar(&debugGlobal, "debug", false, "Show debugGlobal information") + rootCmd.PersistentFlags().BoolVar(&debugGlobal, "debug", false, "Show debug information") // Add subcommands rootCmd.AddCommand(setKeyCmd) diff --git a/cmd/whois.go b/cmd/whois.go index 6d1d6b3..b3b5dd8 100644 --- a/cmd/whois.go +++ b/cmd/whois.go @@ -10,6 +10,7 @@ import ( "fmt" "github.com/spf13/cobra" "go.uber.org/zap" + "os" "strings" "time" ) @@ -97,15 +98,9 @@ var ( // Show credits if requested if whoisShowCredits { fmt.Println("[*] Getting WHOIS balance...") - balance, err := w.Balance() - if err != nil { - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) + if whoisShowCredits { + checkBalance(w) } - fmt.Printf("WHOIS Credits: %d\n", balance) } // Check if domain is provided for history and subdomain scan @@ -115,15 +110,7 @@ var ( return } if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } } @@ -147,19 +134,7 @@ var ( } if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } // Fix the output format to use proper formatting @@ -212,19 +187,7 @@ var ( fmt.Printf("[!] Error performing WHOIS history lookup: %v\n", err) } else { if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } // Write history records to file if any @@ -274,19 +237,7 @@ var ( subdomains, err := w.WhoisSubdomainScan(whoisDomain) if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } if err != nil { @@ -371,19 +322,7 @@ var ( // Get credits if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } if len(result) == 0 { @@ -445,19 +384,7 @@ var ( // Get credits if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } if len(result) == 0 { @@ -519,19 +446,7 @@ var ( // Get credits if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } if len(result) == 0 { @@ -625,19 +540,7 @@ var ( fmt.Println(result) if whoisShowCredits { - balance, err := w.Balance() - if err != nil { - if debugGlobal { - debug.PrintInfo("failed to get whois balance") - debug.PrintError(err) - } - zap.L().Error("get_whois_credits", - zap.String("message", "failed to get whois balance"), - zap.Error(err), - ) - fmt.Printf("Error getting WHOIS balance: %v\n", err) - } - fmt.Println("WHOIS Credits: ", balance) + checkBalance(w) } return } @@ -647,3 +550,23 @@ var ( }, } ) + +func checkBalance(w *whois.DehashedWhoIs) { + balance, err := w.Balance() + if err != nil { + if debugGlobal { + debug.PrintInfo("failed to get whois balance") + debug.PrintError(err) + } + zap.L().Error("get_whois_credits", + zap.String("message", "failed to get whois balance"), + zap.Error(err), + ) + fmt.Printf("Error getting WHOIS balance: %v\n", err) + } + fmt.Println("WHOIS Credits: ", balance) + if balance == 0 { + fmt.Println("[!] No WHOIS credits remaining.") + os.Exit(0) + } +} diff --git a/internal/easyTime/parser.go b/internal/easyTime/parser.go new file mode 100644 index 0000000..879dd3b --- /dev/null +++ b/internal/easyTime/parser.go @@ -0,0 +1,266 @@ +package easyTime + +import ( + "dehasher/internal/debug" + "fmt" + "go.uber.org/zap" + "os" + "strconv" + "strings" + "time" +) + +type TimeChunk struct { + StartTime time.Time + EndTime time.Time + set bool +} + +func (tc *TimeChunk) isValid() bool { + if !tc.StartTime.IsZero() && !tc.EndTime.IsZero() && tc.StartTime.Before(tc.EndTime) { + tc.set = true + return true + } + tc.set = false + return false +} + +func (tc *TimeChunk) IsSet() bool { + return tc.set +} + +func NewTimeChunk(start, end string, debugOn bool) TimeChunk { + if debugOn { + debug.PrintInfo("parsing time chunk") + debug.PrintInfo(fmt.Sprintf("Start: %s, End: %s", start, end)) + zap.L().Info("parsing time chunk", + zap.String("start", start), + zap.String("end", end), + ) + } + + if end == "" { + if debugOn { + debug.PrintInfo("no end time provided, using now") + } + end = "now" + } + + tc := TimeChunk{ + StartTime: parseUserTime(start), + EndTime: parseUserTime(end), + } + + if debugOn { + debug.PrintInfo("checking if time chunk is valid") + debug.PrintInfo(fmt.Sprintf("Start: %s, End: %s", tc.StartTime, tc.EndTime)) + } + if !tc.isValid() { + fmt.Println("[!] Invalid time chunk") + zap.L().Fatal("invalid_time_chunk", + zap.String("message", "invalid time chunk"), + ) + os.Exit(1) + } + + return tc +} + +func parseUserTime(args string) time.Time { + args = strings.TrimSpace(args) + + if strings.EqualFold(args, "now") { + return time.Now() + } + + // Check if time contains a space, if so, it's in 'last 24 hours' format + if strings.Contains(args, " ") && !containsMonth(strings.Split(args, " ")) { + splitArgs := strings.Split(args, " ") + if len(splitArgs) == 0 { + fmt.Println("[!] No time provided") + zap.L().Fatal("no_time_provided", + zap.String("message", "no time provided"), + ) + os.Exit(1) + } else if len(splitArgs) < 3 { + fmt.Println("[!] Invalid time format") + zap.L().Fatal("invalid_time_format", + zap.String("message", "invalid time format"), + ) + os.Exit(1) + } + + // Handle 'last 24 hours' format + var ( + tense string + amount int + duration time.Duration + ) + for _, arg := range splitArgs { + if isPasteTense(arg) { + tense = arg + } else if isNumber(arg) { + amount, _ = strconv.Atoi(arg) + } else if isDuration(arg) { + duration = getDuration(arg) + } + } + + if tense == "" { + fmt.Println("[!] Invalid time format: tense not found") + zap.L().Fatal("invalid_time_format", + zap.String("message", "invalid time format"), + ) + os.Exit(1) + } else if amount == 0 { + fmt.Println("[!] Invalid time format: amount not found") + zap.L().Fatal("invalid_time_format", + zap.String("message", "invalid time format"), + ) + os.Exit(1) + } else if duration == 0 { + fmt.Println("[!] Invalid time format: duration not found") + zap.L().Fatal("invalid_time_format", + zap.String("message", "invalid time format"), + ) + os.Exit(1) + } + + // Return the appropriate time + if tense == "last" { + return time.Now().Add(-time.Duration(amount) * duration) + } else if tense == "ago" { + return time.Now().Add(-time.Duration(amount) * duration) + } + } + + // Handle possible formats 'May 01, 2025', '05-01-2025', '05/01/2025', '05/01/25', '05-01-25' + var ( + t time.Time + err error + found bool + ) + possible := []string{"01-02-2006", "01/02/2006", "01/02/06", "01-02-06", "Jan 02, 2006", "Jan 2, 2006"} + for _, format := range possible { + t, err = time.Parse(format, args) + if err == nil { + found = true + break + } + } + + if !found { + fmt.Println("[!] Invalid time format") + zap.L().Fatal("invalid_time_format", + zap.String("message", "invalid time format"), + ) + os.Exit(1) + } + + // Convert UTC time to local time + local, err := time.LoadLocation("Local") + if err != nil { + fmt.Println("[!] Error loading local timezone") + zap.L().Error("load_timezone", + zap.String("message", "failed to load local timezone"), + zap.Error(err), + ) + return t + } + + // Convert the parsed time to local time + return time.Date( + t.Year(), + t.Month(), + t.Day(), + t.Hour(), + t.Minute(), + t.Second(), + t.Nanosecond(), + local, + ) +} + +func isPasteTense(value string) bool { + for _, v := range []string{"last", "ago"} { + if strings.EqualFold(value, v) { + return true + } + } + return false +} + +func isDuration(value string) bool { + for _, v := range []string{"hour", "hours", "minute", "minutes", "second", "seconds", "day", "days", "week", "weeks", "month", "months", "year", "years"} { + if strings.EqualFold(value, v) { + return true + } + } + return false +} + +func isNumber(value string) bool { + _, err := strconv.Atoi(value) + return err == nil +} + +func getDuration(timeBlock string) time.Duration { + timeBlock = strings.TrimSpace(strings.ToLower(timeBlock)) + + switch timeBlock { + case "hour": + return time.Hour + case "hours": + return time.Hour + case "minute": + return time.Minute + case "minutes": + return time.Minute + case "second": + return time.Second + case "seconds": + return time.Second + case "day": + return 24 * time.Hour + case "days": + return 24 * time.Hour + case "week": + return 7 * 24 * time.Hour + case "weeks": + return 7 * 24 * time.Hour + case "month": + return 30 * 24 * time.Hour + case "months": + return 30 * 24 * time.Hour + case "year": + return 365 * 24 * time.Hour + case "years": + return 365 * 24 * time.Hour + default: + fmt.Printf("[!] Unknown duration: %s", timeBlock) + zap.L().Fatal("unknown_duration", + zap.String("message", "unknown duration"), + zap.String("duration", timeBlock), + ) + os.Exit(1) + } + return 0 +} + +func containsMonth(arr []string) bool { + for _, v := range arr { + if isMonth(v) { + return true + } + } + return false +} + +func isMonth(value string) bool { + for _, v := range []string{"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"} { + if strings.EqualFold(value, v) { + return true + } + } + return false +}