From c385a1461fb4a5cd7b2ce460ba9921cbd7d65a7a Mon Sep 17 00:00:00 2001 From: cyclone <34043806+cyclone-github@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:54:20 -0500 Subject: [PATCH] v0.1.0; 2023-08-22.1100 --- README.md | 5 +- src/api_key.go | 113 ++++++++++++++++++++++++++++++++++++++ src/clear_screen.go | 21 +++++++ src/found_history.go | 67 ++++++++++++++++++++++ src/get_api.go | 74 +++++++++++++++++++++++++ src/globals.go | 31 +++++++++++ src/go.mod | 3 + src/hash_identifier.go | 47 ++++++++++++++++ src/main.go | 112 +++++++++++++++++++++++++++++++++++++ src/print_cyclone.go | 21 +++++++ src/remove_api.go | 47 ++++++++++++++++ src/search_hashes.go | 87 +++++++++++++++++++++++++++++ src/show_profit.go | 56 +++++++++++++++++++ src/to_usd.go | 52 ++++++++++++++++++ src/upload_founds.go | 89 ++++++++++++++++++++++++++++++ src/utils.go | 86 +++++++++++++++++++++++++++++ src/wallet_balance.go | 60 ++++++++++++++++++++ src/withdrawal_history.go | 78 ++++++++++++++++++++++++++ 18 files changed, 1046 insertions(+), 3 deletions(-) create mode 100644 src/api_key.go create mode 100644 src/clear_screen.go create mode 100644 src/found_history.go create mode 100644 src/get_api.go create mode 100644 src/globals.go create mode 100644 src/go.mod create mode 100644 src/hash_identifier.go create mode 100644 src/main.go create mode 100644 src/print_cyclone.go create mode 100644 src/remove_api.go create mode 100644 src/search_hashes.go create mode 100644 src/show_profit.go create mode 100644 src/to_usd.go create mode 100644 src/upload_founds.go create mode 100644 src/utils.go create mode 100644 src/wallet_balance.go create mode 100644 src/withdrawal_history.go diff --git a/README.md b/README.md index 39e3044..f3a4fed 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Cyclone's Hashes.com API Escrow Tool -(coming soon) ![image](https://i.imgur.com/gNpMGaq.png) @@ -17,12 +16,12 @@ https://github.com/PlumLulz/hashes.com-cli - Paste custom file path - Paste hash:plaintext - Show Upload History -- Search Hashes +- Search Hashes (requires credits from hashes.com) - Hash Identifier - Wallet Balance - Show Profit - Withdraw History - Saves API key locally with AES encrypted key file -###Compile from source code info: +### Compile from source code info: - https://github.com/cyclone-github/scripts/blob/main/intro_to_go.txt diff --git a/src/api_key.go b/src/api_key.go new file mode 100644 index 0000000..377f0eb --- /dev/null +++ b/src/api_key.go @@ -0,0 +1,113 @@ +package main + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "net" + "net/http" +) + +// generate unique encryption key +func getEncryptionKey() { + decodedSeed, err := base64.StdEncoding.DecodeString(base64StaticSeedKey) + if err != nil { + fmt.Printf("Error decoding Base64 static seed key: %v\n") + return + } + + staticSeedKey := string(decodedSeed) + + interfaces, err := net.Interfaces() + if err != nil || len(interfaces) == 0 { + hash := sha256.Sum256([]byte(staticSeedKey)) + encryptionKey = hex.EncodeToString(hash[:]) + return + } + + var macAddr string + for _, i := range interfaces { + if i.HardwareAddr != nil && len(i.HardwareAddr.String()) > 0 { + macAddr = i.HardwareAddr.String() + break + } + } + + seed := macAddr + staticSeedKey + hash := sha256.Sum256([]byte(seed)) + encryptionKey = hex.EncodeToString(hash[:]) +} + +// encrypt key file +func encrypt(data []byte, passphrase string) ([]byte, error) { + block, err := aes.NewCipher(getSHA256Hash(passphrase)) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonce := make([]byte, gcm.NonceSize()) + ciphertext := gcm.Seal(nonce, nonce, data, nil) + return ciphertext, nil +} + +// decrypt key file +func decrypt(data []byte, passphrase string) ([]byte, error) { + key := getSHA256Hash(passphrase) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + nonceSize := gcm.NonceSize() + nonce, ciphertext := data[:nonceSize], data[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + return plaintext, nil +} + +// sha256Hash function for encrypt /decrypt file func +func getSHA256Hash(text string) []byte { + hash := sha256.Sum256([]byte(text)) + return hash[:] +} + +// verify hashes.com API key +func verifyAPIKey(apiKey string) bool { + url := fmt.Sprintf("https://hashes.com/en/api/balance?key=%s", apiKey) + + resp, err := http.Get(url) + if err != nil { + fmt.Printf("Failed to send request: %v\n", err) + return false + } + defer resp.Body.Close() + + var response struct { + Success bool `json:"success"` + } + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + fmt.Printf("Failed to decode response: %v\n", err) + return false + } + + if response.Success { + fmt.Println("API key verified") + return true + } + + //fmt.Println("API key not verified, try again") + return false +} diff --git a/src/clear_screen.go b/src/clear_screen.go new file mode 100644 index 0000000..77bfd1d --- /dev/null +++ b/src/clear_screen.go @@ -0,0 +1,21 @@ +package main + +import ( + "os" + "os/exec" + "runtime" +) + +// clear screen function +func clearScreen() { + switch runtime.GOOS { + case "linux", "darwin": + cmd := exec.Command("clear") + cmd.Stdout = os.Stdout + cmd.Run() + case "windows": + cmd := exec.Command("cmd", "/c", "cls") + cmd.Stdout = os.Stdout + cmd.Run() + } +} \ No newline at end of file diff --git a/src/found_history.go b/src/found_history.go new file mode 100644 index 0000000..59e232f --- /dev/null +++ b/src/found_history.go @@ -0,0 +1,67 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "text/tabwriter" +) + +// case 2, get found history +func getFoundHistory(apiKey string) error { + url := fmt.Sprintf("https://hashes.com/en/api/uploads?key=%s", apiKey) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + var response struct { + Success bool `json:"success"` + List []FoundHistory `json:"list"` + } + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return fmt.Errorf("failed to decode response: %v", err) + } + + if !response.Success { + return fmt.Errorf("request was not successful") + } + + fmt.Println("Upload History (last 10):\n") + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.AlignRight|tabwriter.Debug) + defer writer.Flush() + + fmt.Fprintln(writer, "ID\tDate\tBTC (USD)\tXMR (USD)\tLTC (USD)\tAlgo\t-m\tFound\tTotal\tStatus") + + btcRate, _ := toUSD(1, "BTC") + xmrRate, _ := toUSD(1, "XMR") + ltcRate, _ := toUSD(1, "LTC") + + startIndex := len(response.List) - 10 + if startIndex < 0 { + startIndex = 0 + } + + for i := startIndex; i < len(response.List); i++ { + history := response.List[i] + + btc := parseFloat(history.BTC) + xmr := parseFloat(history.XMR) + ltc := parseFloat(history.LTC) + + btcUSD := fmt.Sprintf("$%.3f", btc*parseFloat(btcRate["currentprice"].(string))) + xmrUSD := fmt.Sprintf("$%.3f", xmr*parseFloat(xmrRate["currentprice"].(string))) + ltcUSD := fmt.Sprintf("$%.3f", ltc*parseFloat(ltcRate["currentprice"].(string))) + + fmt.Fprintf(writer, "%d\t%s\t%s\t%s\t%s\t%s\t%d\t%d\t%d\t%s\n", + history.ID, history.Date, + btcUSD, xmrUSD, ltcUSD, + history.Algorithm, history.AlgorithmID, history.ValidHashes, history.TotalHashes, history.Status) + } + + return nil +} \ No newline at end of file diff --git a/src/get_api.go b/src/get_api.go new file mode 100644 index 0000000..5391828 --- /dev/null +++ b/src/get_api.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "io/ioutil" + "os" + "time" +) + +// case 8, get new API key +func getAPIKey(promptForNewKey bool) string { + if promptForNewKey { + clearScreen() + for { + fmt.Println("Enter your API key from hashes.com/profile") + var newAPIKey string + fmt.Scanln(&newAPIKey) + + if verifyAPIKey(newAPIKey) { + encryptedKey, err := encrypt([]byte(newAPIKey), encryptionKey) + if err != nil { + fmt.Printf("Error encrypting API key: %v\n", err) + continue + } + err = ioutil.WriteFile(apiKeyFile, encryptedKey, 0644) + if err != nil { + fmt.Printf("Error writing encrypted API key to file: %v\n", err) + continue + } + return newAPIKey + } else { + fmt.Println("API key not verified, try again.") + time.Sleep(500 * time.Millisecond) + } + } + } + + for { + var apiKey string + + if _, err := os.Stat(apiKeyFile); err == nil { + encryptedKey, err := ioutil.ReadFile(apiKeyFile) + if err != nil { + fmt.Printf("Error reading API key file: %v\n", err) + } else { + decryptedKey, err := decrypt(encryptedKey, encryptionKey) + if err != nil { + fmt.Printf("Error decrypting API key: %v\n", err) + } else { + apiKey = string(decryptedKey) + if verifyAPIKey(apiKey) { + return apiKey + } + } + } + } + + fmt.Println("Enter your API key from hashes.com/profile") + fmt.Scanln(&apiKey) + encryptedKey, err := encrypt([]byte(apiKey), encryptionKey) + if err != nil { + fmt.Printf("Error encrypting API key: %v\n", err) + continue + } + err = ioutil.WriteFile(apiKeyFile, encryptedKey, 0644) + if err != nil { + fmt.Printf("Error writing encrypted API key to file: %v\n", err) + continue + } + if verifyAPIKey(apiKey) { + return apiKey + } + } +} \ No newline at end of file diff --git a/src/globals.go b/src/globals.go new file mode 100644 index 0000000..f6faf4a --- /dev/null +++ b/src/globals.go @@ -0,0 +1,31 @@ +package main + +// global structs, constants and variables +type FoundHistory struct { + ID int `json:"id"` + BTC string `json:"btc"` + XMR string `json:"xmr"` + LTC string `json:"ltc"` + Date string `json:"date"` + TotalHashes int `json:"totalHashes"` + ValidHashes int `json:"validHashes"` + Status string `json:"status"` + Algorithm string `json:"algorithm"` + AlgorithmID int `json:"algorithmId"` +} + +type WalletBalances struct { + BTC string `json:"BTC"` + XMR string `json:"XMR"` + LTC string `json:"LTC"` + Credits string `json:"credits"` +} + +const ( + apiKeyFile = "api_key.enc" + base64StaticSeedKey = "NWl5cTk3RlEwZy9HODFBQTU3NF5lZU0lel0zSwo=" +) + +var ( + encryptionKey string +) \ No newline at end of file diff --git a/src/go.mod b/src/go.mod new file mode 100644 index 0000000..10238c7 --- /dev/null +++ b/src/go.mod @@ -0,0 +1,3 @@ +module main.go + +go 1.20 diff --git a/src/hash_identifier.go b/src/hash_identifier.go new file mode 100644 index 0000000..127d1d0 --- /dev/null +++ b/src/hash_identifier.go @@ -0,0 +1,47 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" +) + +// case 4, hash identifier +func hashIdentifier(hash string, extended bool) error { + url := "https://hashes.com/en/api/identifier?hash=" + hash + if extended { + url += "&extended=true" + } + + resp, err := http.Get(url) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + var result map[string]interface{} + if err := json.Unmarshal(body, &result); err != nil { + return err + } + + if success, ok := result["success"].(bool); ok && success { + if algorithms, ok := result["algorithms"].([]interface{}); ok && len(algorithms) > 0 { + fmt.Println("Possible Algorithms:") + for _, algo := range algorithms { + fmt.Printf(" %s\n", algo) + } + } else { + fmt.Println("No algorithms found.") + } + } else { + fmt.Println("No results found.") + } + + return nil +} \ No newline at end of file diff --git a/src/main.go b/src/main.go new file mode 100644 index 0000000..bc5155e --- /dev/null +++ b/src/main.go @@ -0,0 +1,112 @@ +package main + +import ( + "fmt" + "strings" + "time" +) + +// version history +// v0.1.0; 2023-08-22.1100; initial github release + +// main function +func main() { + clearScreen() + printCyclone() + fmt.Println(" ######################################################################") + fmt.Println("# Cyclone's Hashes.com API Escrow Tool v0.1.0 #") + fmt.Println("# This tool requires an API key from hashes.com #") + fmt.Println("# 'Search Hashes' requires credits #") + fmt.Println("# See hashes.com for more info #") + fmt.Println(" ######################################################################\n") + // check for API key + getEncryptionKey() + apiKey := getAPIKey(false) + + for { + // CLI Menu + time.Sleep(100 * time.Millisecond) + fmt.Println("\nSelect an option:") + fmt.Println("1. Upload Founds") + fmt.Println("2. Upload History") + fmt.Println("3. Search Hashes") + fmt.Println("4. Hash Identifier") + fmt.Println("5. Wallet Balance") + fmt.Println("6. Show Profit") + fmt.Println("7. Withdrawal History") + fmt.Println("8. Enter New API") + fmt.Println("9. Remove API Key") + fmt.Println("c. Clear Screen") + fmt.Println("q. Quit") + var choice string + fmt.Scanln(&choice) + + switch strings.ToLower(choice) { + case "1": + // Upload Founds + clearScreen() + if err := uploadFounds(apiKey); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "2": + // Show Upload History + clearScreen() + if err := getFoundHistory(apiKey); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "3": + // Search Hashes + clearScreen() + hashes := getHashesFromUser() + if err := searchHashes(apiKey, hashes); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "4": + // Hash Identifier + clearScreen() + hashes := getHashesFromUser() + if len(hashes) == 0 { + fmt.Println("No hash provided.") + break + } + hash := hashes[0] + if err := hashIdentifier(hash, true); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "5": + // Wallet Balance + clearScreen() + if err := getWalletBalances(apiKey); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "6": + // Show Profit + clearScreen() + if err := getProfit(apiKey); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "7": + // Withdrawal History + clearScreen() + if err := withdrawalHistory(apiKey); err != nil { + fmt.Printf("An error occurred: %v\n", err) + } + case "8": + // Enter New API + getAPIKey(true) + case "9": + // Remove API Key + clearScreen() + removeAPIKey() + case "c": + // clear screen + clearScreen() + case "q": + // exit program + return + default: + fmt.Println("Invalid choice, please try again.") + time.Sleep(500 * time.Millisecond) + } + } +} \ No newline at end of file diff --git a/src/print_cyclone.go b/src/print_cyclone.go new file mode 100644 index 0000000..d218fc3 --- /dev/null +++ b/src/print_cyclone.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "time" +) + +func printCyclone() { + clearScreen() + cyclone := ` + _ + ____ _ _ ____| | ___ ____ _____ + / ___) | | |/ ___) |/ _ \| _ \| ___ | +( (___| |_| ( (___| | |_| | | | | ____| + \____)\__ |\____)\_)___/|_| |_|_____) + (____/ +` + fmt.Println(cyclone) + time.Sleep(1 * time.Second) + clearScreen() +} \ No newline at end of file diff --git a/src/remove_api.go b/src/remove_api.go new file mode 100644 index 0000000..17069d4 --- /dev/null +++ b/src/remove_api.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "os" + "strings" +) + +// case 9, remove API key +func removeAPIKey() { + fmt.Println("Are you sure you want to remove your API key? (y/n):") + var confirmation string + fmt.Scanln(&confirmation) + + if strings.ToLower(confirmation) == "y" { + fileInfo, err := os.Stat(apiKeyFile) + if err != nil { + fmt.Printf("Error getting API key file info: %v\n", err) + return + } + + file, err := os.OpenFile(apiKeyFile, os.O_WRONLY, 0644) + if err != nil { + fmt.Printf("Error opening API key file: %v\n", err) + return + } + + zeroBytes := make([]byte, fileInfo.Size()) + _, err = file.Write(zeroBytes) + if err != nil { + fmt.Printf("Error overwriting API key file with zeroes: %v\n", err) + return + } + + file.Close() + + err = os.Remove(apiKeyFile) + if err != nil { + fmt.Printf("Error removing API key file: %v\n", err) + } else { + fmt.Println("API key removed successfully. Exiting...") + os.Exit(0) + } + } else { + fmt.Println("API key removal canceled.") + } +} \ No newline at end of file diff --git a/src/search_hashes.go b/src/search_hashes.go new file mode 100644 index 0000000..84a3e18 --- /dev/null +++ b/src/search_hashes.go @@ -0,0 +1,87 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "mime/multipart" + "net/http" + "os" + "text/tabwriter" +) + +// case 3, search hashes +func searchHashes(apiKey string, hashes []string) error { + var requestBody bytes.Buffer + writer := multipart.NewWriter(&requestBody) + _ = writer.WriteField("key", apiKey) + for _, hash := range hashes { + _ = writer.WriteField("hashes[]", hash) + } + writer.Close() + + req, err := http.NewRequest("POST", "https://hashes.com/en/api/search", &requestBody) + if err != nil { + return err + } + + req.Header.Set("Content-Type", writer.FormDataContentType()) + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + + var results map[string]interface{} + if err := json.Unmarshal(body, &results); err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', tabwriter.Debug) + defer w.Flush() + + founds, found := results["founds"] + if !found { + fmt.Println("No results found.") + return nil + } + + foundsSlice, ok := founds.([]interface{}) + if !ok { + fmt.Println("Unexpected results format.") + return nil + } + + if len(foundsSlice) > 0 { + for _, foundItem := range foundsSlice { + foundMap, ok := foundItem.(map[string]interface{}) + if !ok { + fmt.Println("Unexpected item format.") + continue + } + + for key, value := range foundMap { + if key != "salt" || fmt.Sprint(value) != "" { + fmt.Fprintf(w, "%s:\t%s\n", key, fmt.Sprint(value)) + } + } + fmt.Println() + } + } else { + unfounds, unfound := results["unfounds"] + if unfound { + unfoundHashes, ok := unfounds.([]interface{}) + if ok && len(unfoundHashes) > 0 { + fmt.Println("Hash not found.") + } + } + } + + return nil +} \ No newline at end of file diff --git a/src/show_profit.go b/src/show_profit.go new file mode 100644 index 0000000..1e1bbbc --- /dev/null +++ b/src/show_profit.go @@ -0,0 +1,56 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "text/tabwriter" +) + +// case 6, show profit +func getProfit(apiKey string) error { + url := fmt.Sprintf("https://hashes.com/en/api/profit?key=%s", apiKey) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + var response struct { + Success bool `json:"success"` + Currency map[string]string `json:"currency"` + } + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return fmt.Errorf("failed to decode response: %v", err) + } + + if !response.Success { + return fmt.Errorf("request was not successful") + } + + usd := make(map[string]string) + for currency, value := range response.Currency { + if valueFloat, err := strconv.ParseFloat(value, 64); err == nil { + usdValue, _ := toUSD(valueFloat, currency) + usdValueConverted, ok := usdValue["converted"] + if ok { + usd[currency] = fmt.Sprintf("%v", usdValueConverted) + } + } + } + + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.AlignRight|tabwriter.Debug) + fmt.Fprintln(writer, "Total Profit:") + fmt.Fprintln(writer, "Crypto \t Coins \t USD") + + for currency, amount := range response.Currency { + fmt.Fprintf(writer, "%s \t %s \t %s\n", currency, amount, usd[currency]) + } + + writer.Flush() + return nil +} \ No newline at end of file diff --git a/src/to_usd.go b/src/to_usd.go new file mode 100644 index 0000000..facb593 --- /dev/null +++ b/src/to_usd.go @@ -0,0 +1,52 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" +) + +// convert crypto to usd via Kraken API +func toUSD(value float64, currency string) (map[string]interface{}, error) { + if currency == "credits" { + return map[string]interface{}{"currentprice": nil, "converted": "N/A"}, nil + } + + url := fmt.Sprintf("https://api.kraken.com/0/public/Ticker?pair=%susd", currency) + resp, err := http.Get(url) + if err != nil { + return nil, fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + var response struct { + Result map[string]struct { + A []string `json:"a"` + } `json:"result"` + } + + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return nil, fmt.Errorf("failed to decode response: %v", err) + } + + var currentPrice string + switch strings.ToUpper(currency) { + case "BTC": + currentPrice = response.Result["XXBTZUSD"].A[0] + case "XMR": + currentPrice = response.Result["XXMRZUSD"].A[0] + case "LTC": + currentPrice = response.Result["XLTCZUSD"].A[0] + } + + currentPriceFloat, err := strconv.ParseFloat(currentPrice, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse current price: %v", err) + } + + converted := fmt.Sprintf("$%.3f", value*currentPriceFloat) + + return map[string]interface{}{"currentprice": currentPrice, "converted": converted}, nil +} \ No newline at end of file diff --git a/src/upload_founds.go b/src/upload_founds.go new file mode 100644 index 0000000..7f8da0e --- /dev/null +++ b/src/upload_founds.go @@ -0,0 +1,89 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "mime/multipart" + "net/http" + "os" + "time" +) + +// case 1, upload founds +func uploadFounds(apiKey string) error { + clearScreen() + fmt.Println("Upload Founds:\n") + filePath, hashPlaintext := selectFile() + var file io.Reader + var filename string + if filePath == "PASTE" { + file = bytes.NewBufferString(hashPlaintext) + filename = time.Now().Format("150405") + ".txt" + } else if filePath == "" { + return nil + } else { + f, err := os.Open(filePath) + if err != nil { + return fmt.Errorf("An error occurred: failed to open file: %v", err) + } + defer f.Close() + file = f + filename = filePath + } + + fmt.Println("Enter algorithm ID:") + var algo string + fmt.Scanln(&algo) + + var requestBody bytes.Buffer + writer := multipart.NewWriter(&requestBody) + + _ = writer.WriteField("key", apiKey) + _ = writer.WriteField("algo", algo) + + filePart, err := writer.CreateFormFile("userfile", filename) + if err != nil { + return fmt.Errorf("An error occurred: failed to create form file: %v", err) + } + _, err = io.Copy(filePart, file) + if err != nil { + return fmt.Errorf("An error occurred: failed to copy file: %v", err) + } + + err = writer.Close() + if err != nil { + return fmt.Errorf("An error occurred: failed to close writer: %v", err) + } + + url := "https://hashes.com/en/api/founds" + req, err := http.NewRequest(http.MethodPost, url, &requestBody) + if err != nil { + return fmt.Errorf("An error occurred: failed to create request: %v", err) + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("An error occurred: failed to send request: %v", err) + } + defer resp.Body.Close() + + var response struct { + Success bool `json:"success"` + } + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return fmt.Errorf("An error occurred: failed to decode response: %v", err) + } + + if !response.Success { + return fmt.Errorf("Upload failed") + } + + fmt.Println("Upload Successful") + + return nil +} \ No newline at end of file diff --git a/src/utils.go b/src/utils.go new file mode 100644 index 0000000..0351a40 --- /dev/null +++ b/src/utils.go @@ -0,0 +1,86 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" +) + +// parse string to float64 +func parseFloat(value string) float64 { + f, _ := strconv.ParseFloat(value, 64) + return f +} + +// get hashes from user +func getHashesFromUser() []string { + fmt.Println("This service is provided by hashes.com API") + //fmt.Println("Enter the hashes separated by commas or spaces:\n") + fmt.Println("Paste a single hash:\n") + var input string + fmt.Scanln(&input) + + // Replace commas with spaces and then split by spaces + hashes := strings.Fields(strings.NewReplacer(",", "", " ", "").Replace(input)) + + return hashes +} + +// select file +func selectFile() (string, string) { + files, err := filepath.Glob("*.txt") + if err != nil { + fmt.Printf("Error reading files: %v\n", err) + return "", "" + } + + for { + fmt.Println("Select a file to upload:") + for i, file := range files { + fmt.Printf("%d. %s\n", i+1, file) + } + fmt.Println("p. Paste hash:plaintext") + fmt.Println("c. Custom file path") + fmt.Println("m. Return to Menu") + + var input string + fmt.Scanln(&input) + input = strings.ToLower(input) + + if input == "m" { + clearScreen() + return "", "" // return empty strings to return to the menu + } + + if input == "p" { + fmt.Println("Paste hash:plaintext (press Enter twice to finish):") + reader := bufio.NewReader(os.Stdin) + var hashPlaintexts []string + for { + line, err := reader.ReadString('\n') + if err != nil || strings.TrimSpace(line) == "" { + break + } + hashPlaintexts = append(hashPlaintexts, strings.TrimSpace(line)) + } + return "PASTE", strings.Join(hashPlaintexts, "\n") + } + + if input == "c" { + fmt.Println("Enter the full path of the file:") + var customFilePath string + fmt.Scanln(&customFilePath) + return customFilePath, "" + } + + choice, err := strconv.Atoi(input) + if err == nil && choice > 0 && choice <= len(files) { + return files[choice-1], "" + } + + fmt.Println("Invalid selection. Try again.") + } +} \ No newline at end of file diff --git a/src/wallet_balance.go b/src/wallet_balance.go new file mode 100644 index 0000000..aa001e8 --- /dev/null +++ b/src/wallet_balance.go @@ -0,0 +1,60 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "text/tabwriter" +) + +// case 5, get wallet balance +func getWalletBalances(apiKey string) error { + url := fmt.Sprintf("https://hashes.com/en/api/balance?key=%s", apiKey) + + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("failed to send request: %v", err) + } + defer resp.Body.Close() + + var response struct { + Success bool `json:"success"` + WalletBalances + } + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return fmt.Errorf("failed to decode response: %v", err) + } + + if !response.Success { + return fmt.Errorf("request was not successful") + } + + walletBalances := response.WalletBalances + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.AlignRight|tabwriter.Debug) + fmt.Fprintln(writer, "Crypto \t Coins \t USD") + + // Convert and print balances for BTC, XMR, LTC + for _, crypto := range []struct{ name, value string }{ + {"BTC", walletBalances.BTC}, + {"XMR", walletBalances.XMR}, + {"LTC", walletBalances.LTC}, + } { + amount, err := strconv.ParseFloat(crypto.value, 64) + if err != nil { + return fmt.Errorf("Error parsing %s amount: %v\n", crypto.name, err) + } + cryptoUSD, err := toUSD(amount, crypto.name) + if err != nil { + return fmt.Errorf("Error converting %s: %v\n", crypto.name, err) + } + fmt.Fprintf(writer, "%s \t %s \t %s\n", crypto.name, crypto.value, cryptoUSD["converted"]) + } + + fmt.Fprintf(writer, "Credits \t %s\n", walletBalances.Credits) + writer.Flush() + + return nil +} \ No newline at end of file diff --git a/src/withdrawal_history.go b/src/withdrawal_history.go new file mode 100644 index 0000000..4b48b5f --- /dev/null +++ b/src/withdrawal_history.go @@ -0,0 +1,78 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + "strconv" + "text/tabwriter" +) + +// case 7, withdrawal history +func withdrawalHistory(apiKey string) error { + fmt.Println("Withdrawal History:\n") + url := "https://hashes.com/en/api/withdrawals?key=" + apiKey + resp, err := http.Get(url) + if err != nil { + return fmt.Errorf("An error occurred: failed to send request: %v", err) + } + defer resp.Body.Close() + + var response struct { + Success bool `json:"success"` + List []struct { + ID string `json:"id"` + Amount string `json:"amount"` + AfterFee string `json:"afterFee"` + Transaction string `json:"transaction"` + Currency string `json:"currency"` + Destination string `json:"destination"` + Date string `json:"date"` + } `json:"list"` + } + + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return fmt.Errorf("An error occurred: failed to decode response: %v", err) + } + + end := 10 + if end > len(response.List) { + end = len(response.List) + } + first10 := response.List[:end] + + for i, j := 0, len(first10)-1; i < j; i, j = i+1, j-1 { + first10[i], first10[j] = first10[j], first10[i] + } + + uniqueCurrencies := map[string]float64{} + for _, withdrawal := range first10 { + currency := withdrawal.Currency + if _, exists := uniqueCurrencies[currency]; !exists { + rate, err := toUSD(1, currency) + if err != nil { + return fmt.Errorf("An error occurred: %v", err) + } + currentPrice, _ := strconv.ParseFloat(rate["currentprice"].(string), 64) + uniqueCurrencies[currency] = currentPrice + } + } + + writer := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', tabwriter.Debug) + fmt.Fprintln(writer, "Date\t Amount USD \t After Fee \t ID \t Crypto \t Transaction ID \t Destination Wallet") + + for _, withdrawal := range first10 { + amount, _ := strconv.ParseFloat(withdrawal.Amount, 64) + afterFee, _ := strconv.ParseFloat(withdrawal.AfterFee, 64) + conversionRate := uniqueCurrencies[withdrawal.Currency] + destinationWallet := withdrawal.Destination + + fmt.Fprintf(writer, "%s \t $%.3f \t $%.3f \t %s \t %s \t %s \t %s\n", + withdrawal.Date, amount*conversionRate, afterFee*conversionRate, withdrawal.ID, withdrawal.Currency, withdrawal.Transaction, destinationWallet) + } + + writer.Flush() + return nil +} \ No newline at end of file