+
### Usage
Make an account at [**Space Track**](https://space-track.org) save username and password.
diff --git a/cli/cli.go b/cli/cli.go
index bca8919..6d76873 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -1,68 +1,109 @@
package cli
import (
+ "bufio"
"fmt"
- "io/ioutil"
- "github.com/iskaa02/qalam/gradient"
- "github.com/TwiN/go-color"
- "github.com/ANG13T/SatIntel/osint"
- "strconv"
"os"
+ "os/exec"
+ "runtime"
+ "strconv"
+
+ "github.com/ANG13T/SatIntel/osint"
+ "github.com/TwiN/go-color"
+ "github.com/iskaa02/qalam/gradient"
)
+// Option prompts the user to select a menu option and validates the input.
+// It recursively prompts until a valid option between 0 and 4 is entered.
func Option() {
fmt.Print("\n ENTER INPUT > ")
var selection string
fmt.Scanln(&selection)
num, err := strconv.Atoi(selection)
- if err != nil {
+ if err != nil {
fmt.Println(color.Ize(color.Red, " [!] INVALID INPUT"))
Option()
- } else {
- if (num >= 0 && num < 5) {
+ } else {
+ if num >= 0 && num < 6 {
DisplayFunctions(num)
} else {
fmt.Println(color.Ize(color.Red, " [!] INVALID INPUT"))
Option()
}
- }
+ }
}
+// DisplayFunctions executes the selected function based on the menu choice.
+// After execution, it waits for user input, clears the screen, and shows the menu again.
func DisplayFunctions(x int) {
- if (x == 0) {
+ if x == 0 {
fmt.Println(color.Ize(color.Blue, " Escaping Orbit..."))
os.Exit(1)
- } else if (x == 1) {
+ } else if x == 1 {
osint.OrbitalElement()
+ waitForEnter()
+ clearScreen()
Banner()
Option()
- } else if (x == 2) {
+ } else if x == 2 {
osint.SatellitePositionVisualization()
+ waitForEnter()
+ clearScreen()
Banner()
Option()
- } else if (x == 3) {
+ } else if x == 3 {
osint.OrbitalPrediction()
+ waitForEnter()
+ clearScreen()
Banner()
Option()
- }else if (x == 4) {
+ } else if x == 4 {
osint.TLEParser()
+ waitForEnter()
+ clearScreen()
+ Banner()
+ Option()
+ } else if x == 5 {
+ osint.BatchOperations()
+ waitForEnter()
+ clearScreen()
Banner()
Option()
}
}
+// Banner displays the application banner, info, and menu options with gradient colors.
func Banner() {
- banner, _ := ioutil.ReadFile("txt/banner.txt")
- info, _ := ioutil.ReadFile("txt/info.txt")
- options, _ := ioutil.ReadFile("txt/options.txt")
- g,_:=gradient.NewGradient("cyan", "blue")
- solid,_:=gradient.NewGradient("blue", "#1179ef")
- opt,_:=gradient.NewGradient("#1179ef", "cyan")
+ banner, _ := os.ReadFile("txt/banner.txt")
+ info, _ := os.ReadFile("txt/info.txt")
+ options, _ := os.ReadFile("txt/options.txt")
+ g, _ := gradient.NewGradient("cyan", "blue")
+ solid, _ := gradient.NewGradient("blue", "#1179ef")
+ opt, _ := gradient.NewGradient("#1179ef", "cyan")
g.Print(string(banner))
solid.Print(string(info))
opt.Print("\n" + string(options))
}
+// waitForEnter pauses execution and waits for the user to press Enter.
+func waitForEnter() {
+ fmt.Print("\n\nPress Enter to continue...")
+ bufio.NewReader(os.Stdin).ReadBytes('\n')
+}
+
+// clearScreen clears the terminal screen using the appropriate command for the operating system.
+func clearScreen() {
+ var cmd *exec.Cmd
+ if runtime.GOOS == "windows" {
+ cmd = exec.Command("cmd", "/c", "cls")
+ } else {
+ cmd = exec.Command("clear")
+ }
+ cmd.Stdout = os.Stdout
+ cmd.Run()
+}
+
+// SatIntel initializes the SatIntel CLI application by displaying the banner and starting the menu loop.
func SatIntel() {
Banner()
Option()
diff --git a/cli/cli_test.go b/cli/cli_test.go
new file mode 100644
index 0000000..cf048bd
--- /dev/null
+++ b/cli/cli_test.go
@@ -0,0 +1,60 @@
+package cli
+
+import (
+ "testing"
+)
+
+// Note: Most CLI functions are interactive and difficult to test without mocking stdin/stdout
+// These tests focus on testable logic and structure validation
+
+func TestGenRowString(t *testing.T) {
+ // This is a helper function that might be used in CLI package
+ // If GenRowString is in osint package, this test can be removed
+ // or we can test the formatting logic here
+ tests := []struct {
+ name string
+ intro string
+ input string
+ checkLen bool
+ }{
+ {
+ name: "Normal case",
+ intro: "Test",
+ input: "Value",
+ checkLen: true,
+ },
+ {
+ name: "Empty values",
+ intro: "",
+ input: "",
+ checkLen: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // This is a placeholder - adjust based on actual CLI functions
+ // If there are no directly testable functions, we can test file reading, etc.
+ _ = tt
+ })
+ }
+}
+
+// Test file reading functions (if they become testable)
+func TestBannerFileReading(t *testing.T) {
+ // Test that banner files can be read
+ // This is a basic smoke test
+ files := []string{
+ "txt/banner.txt",
+ "txt/info.txt",
+ "txt/options.txt",
+ }
+
+ for _, file := range files {
+ t.Run("Read_"+file, func(t *testing.T) {
+ // This would require the files to exist
+ // For now, this is a placeholder structure
+ _ = file
+ })
+ }
+}
diff --git a/coverage.out b/coverage.out
new file mode 100644
index 0000000..b837a62
--- /dev/null
+++ b/coverage.out
@@ -0,0 +1,920 @@
+mode: set
+github.com/ANG13T/SatIntel/osint/batch.go:52.29,69.28 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:69.28,71.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:73.2,74.24 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:74.24,76.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:77.2,77.11 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:81.50,85.6 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:85.6,106.29 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:106.29,108.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:110.3,110.14 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:111.10,113.20 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:113.20,115.28 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:115.28,118.20 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:118.20,121.21 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:121.21,123.71 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:123.71,133.9 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:133.14,144.9 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:145.13,156.8 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:157.12,168.7 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:169.11,171.6 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:174.10,177.40 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:177.40,178.40 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:178.40,180.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:181.6,181.16 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:184.4,185.33 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:185.33,187.28 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:187.28,190.20 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:190.20,193.21 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:193.21,195.71 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:195.71,205.9 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:205.14,214.9 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:215.13,224.8 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:225.12,234.7 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:235.11,237.6 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:240.10,242.23 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:242.23,244.28 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:244.28,254.6 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:254.11,256.6 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:259.10,260.26 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:260.26,262.13 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:264.4,265.33 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:265.33,267.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:268.4,275.47 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:275.47,280.5 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:282.10,283.26 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:283.26,285.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:285.10,287.34 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:287.34,289.34 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:289.34,291.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:292.6,292.37 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:292.37,294.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:298.10,299.25 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:299.25,306.59 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:306.59,310.6 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:313.10,314.26 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:314.26,316.13 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:318.4,318.19 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:324.69,325.26 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:325.26,327.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:329.2,332.16 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:332.16,335.3 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:337.2,342.33 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:342.33,344.46 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:344.46,354.18 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:354.18,361.5 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:363.4,366.23 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:366.23,369.5 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:369.10,371.27 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:371.27,373.17 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:373.17,375.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:376.6,376.30 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:376.30,378.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:379.6,380.49 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:381.11,388.6 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:391.4,391.78 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:391.78,398.5 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:400.4,405.52 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:405.52,412.5 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:414.4,414.106 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:414.106,421.5 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:423.4,431.116 7 0
+github.com/ANG13T/SatIntel/osint/batch.go:435.2,439.28 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:439.28,440.16 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:440.16,442.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:445.2,447.16 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:451.72,452.23 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:452.23,454.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:456.2,467.33 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:467.33,468.21 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:468.21,472.39 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:472.39,474.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:475.4,475.33 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:475.33,477.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:479.4,479.33 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:479.33,485.21 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:485.21,487.6 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:489.9,491.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:495.2,495.27 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:495.27,497.36 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:497.36,499.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:500.3,500.75 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:503.2,503.26 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:503.26,505.34 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:505.34,507.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:508.3,508.73 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:511.2,511.24 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:511.24,514.33 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:514.33,515.17 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:515.17,517.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:518.4,518.17 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:518.17,520.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:522.3,523.43 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:526.2,526.19 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:530.58,531.34 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:531.34,534.3 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:536.2,544.47 7 0
+github.com/ANG13T/SatIntel/osint/batch.go:544.47,546.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:547.2,547.46 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:547.46,549.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:550.2,550.43 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:550.43,552.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:553.2,553.44 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:553.44,555.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:557.2,561.44 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:561.44,563.22 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:563.22,565.27 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:565.27,567.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:570.3,574.21 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:574.21,578.4 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:580.3,580.36 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:580.36,582.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:585.2,585.235 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:589.24,591.21 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:591.21,593.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:595.2,596.26 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:596.26,598.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:600.2,600.19 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:601.13,603.23 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:603.23,606.35 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:606.35,607.23 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:607.23,610.6 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:610.11,612.29 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:612.29,614.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:614.12,616.7 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:621.4,627.63 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:627.63,629.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:632.17,634.23 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:634.23,645.63 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:645.63,647.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:650.37,651.92 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:657.47,663.34 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:663.34,665.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:667.2,673.16 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:673.16,675.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:677.2,678.20 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:678.20,680.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:682.2,683.22 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:684.13,685.15 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:686.14,687.16 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:688.14,689.15 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:692.2,692.39 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:692.39,694.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:696.2,696.22 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:697.13,698.39 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:699.14,700.40 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:701.14,702.40 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:707.62,713.34 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:713.34,715.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:717.2,723.16 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:723.16,725.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:727.2,728.20 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:728.20,730.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:732.2,733.22 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:734.13,735.15 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:736.14,737.16 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:738.14,739.15 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:742.2,742.39 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:742.39,744.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:746.2,746.22 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:747.13,748.49 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:749.14,750.50 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:751.14,752.50 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:757.73,759.16 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:759.16,761.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:762.2,772.46 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:772.46,774.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:777.2,777.33 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:777.33,780.22 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:780.22,782.27 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:782.27,784.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:787.3,793.21 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:793.21,802.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:802.9,804.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:806.3,806.43 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:806.43,808.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:811.2,812.12 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:816.74,825.33 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:825.33,830.21 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:830.21,832.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:833.3,833.26 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:833.26,835.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:836.3,836.54 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:839.2,846.16 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:846.16,848.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:850.2,850.63 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:850.63,852.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:854.2,855.12 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:859.74,867.33 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:867.33,869.21 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:869.21,876.4 6 0
+github.com/ANG13T/SatIntel/osint/batch.go:876.9,878.27 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:878.27,880.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:882.3,882.28 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:885.2,885.79 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:885.79,887.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:889.2,890.12 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:894.88,896.16 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:896.16,898.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:899.2,906.53 5 0
+github.com/ANG13T/SatIntel/osint/batch.go:906.53,908.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:910.2,915.47 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:915.47,917.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:918.2,918.46 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:918.46,920.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:922.2,922.34 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:922.34,923.43 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:923.43,925.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:929.2,933.52 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:933.52,935.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:937.2,937.44 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:937.44,940.22 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:940.22,942.27 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:942.27,944.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:947.3,953.21 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:953.21,960.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:960.9,962.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:964.3,964.43 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:964.43,966.4 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:969.2,970.12 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:974.89,982.16 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:982.16,984.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:986.2,986.63 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:986.63,988.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:990.2,991.12 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:995.89,1005.47 8 0
+github.com/ANG13T/SatIntel/osint/batch.go:1005.47,1007.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:1008.2,1008.46 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:1008.46,1010.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:1012.2,1015.44 3 0
+github.com/ANG13T/SatIntel/osint/batch.go:1015.44,1017.21 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:1017.21,1022.4 4 0
+github.com/ANG13T/SatIntel/osint/batch.go:1022.9,1024.27 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:1024.27,1026.5 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:1030.2,1032.79 2 0
+github.com/ANG13T/SatIntel/osint/batch.go:1032.79,1034.3 1 0
+github.com/ANG13T/SatIntel/osint/batch.go:1036.2,1037.12 2 0
+github.com/ANG13T/SatIntel/osint/export.go:26.75,35.34 4 0
+github.com/ANG13T/SatIntel/osint/export.go:35.34,37.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:39.2,48.16 4 0
+github.com/ANG13T/SatIntel/osint/export.go:48.16,50.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:52.2,53.20 2 0
+github.com/ANG13T/SatIntel/osint/export.go:53.20,55.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:58.2,60.16 3 0
+github.com/ANG13T/SatIntel/osint/export.go:61.17,62.23 1 0
+github.com/ANG13T/SatIntel/osint/export.go:63.18,64.24 1 0
+github.com/ANG13T/SatIntel/osint/export.go:65.18,66.23 1 0
+github.com/ANG13T/SatIntel/osint/export.go:69.2,69.24 1 0
+github.com/ANG13T/SatIntel/osint/export.go:69.24,71.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:73.2,73.30 1 0
+github.com/ANG13T/SatIntel/osint/export.go:77.69,78.16 1 0
+github.com/ANG13T/SatIntel/osint/export.go:79.17,80.37 1 0
+github.com/ANG13T/SatIntel/osint/export.go:81.18,82.38 1 0
+github.com/ANG13T/SatIntel/osint/export.go:83.18,84.38 1 0
+github.com/ANG13T/SatIntel/osint/export.go:85.10,86.54 1 0
+github.com/ANG13T/SatIntel/osint/export.go:91.51,93.16 2 0
+github.com/ANG13T/SatIntel/osint/export.go:93.16,95.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:96.2,105.46 5 0
+github.com/ANG13T/SatIntel/osint/export.go:105.46,107.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:110.2,132.27 2 0
+github.com/ANG13T/SatIntel/osint/export.go:132.27,133.43 1 0
+github.com/ANG13T/SatIntel/osint/export.go:133.43,135.4 1 0
+github.com/ANG13T/SatIntel/osint/export.go:138.2,138.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:142.52,167.16 3 0
+github.com/ANG13T/SatIntel/osint/export.go:167.16,169.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:171.2,171.63 1 0
+github.com/ANG13T/SatIntel/osint/export.go:171.63,173.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:175.2,175.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:179.52,206.79 24 0
+github.com/ANG13T/SatIntel/osint/export.go:206.79,208.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:210.2,210.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:214.100,215.16 1 0
+github.com/ANG13T/SatIntel/osint/export.go:216.17,217.51 1 0
+github.com/ANG13T/SatIntel/osint/export.go:218.18,219.52 1 0
+github.com/ANG13T/SatIntel/osint/export.go:220.18,221.52 1 0
+github.com/ANG13T/SatIntel/osint/export.go:222.10,223.54 1 0
+github.com/ANG13T/SatIntel/osint/export.go:228.82,230.16 2 0
+github.com/ANG13T/SatIntel/osint/export.go:230.16,232.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:233.2,246.50 6 0
+github.com/ANG13T/SatIntel/osint/export.go:246.50,248.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:249.2,249.46 1 0
+github.com/ANG13T/SatIntel/osint/export.go:249.46,251.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:254.2,254.49 1 0
+github.com/ANG13T/SatIntel/osint/export.go:254.49,256.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:259.2,265.50 2 0
+github.com/ANG13T/SatIntel/osint/export.go:265.50,267.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:270.2,270.35 1 0
+github.com/ANG13T/SatIntel/osint/export.go:270.35,288.43 2 0
+github.com/ANG13T/SatIntel/osint/export.go:288.43,290.4 1 0
+github.com/ANG13T/SatIntel/osint/export.go:293.2,293.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:297.83,310.16 3 0
+github.com/ANG13T/SatIntel/osint/export.go:310.16,312.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:314.2,314.63 1 0
+github.com/ANG13T/SatIntel/osint/export.go:314.63,316.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:318.2,318.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:322.83,337.35 11 0
+github.com/ANG13T/SatIntel/osint/export.go:337.35,347.3 5 0
+github.com/ANG13T/SatIntel/osint/export.go:349.2,351.79 2 0
+github.com/ANG13T/SatIntel/osint/export.go:351.79,353.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:355.2,355.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:359.96,360.16 1 0
+github.com/ANG13T/SatIntel/osint/export.go:361.17,362.50 1 0
+github.com/ANG13T/SatIntel/osint/export.go:363.18,364.51 1 0
+github.com/ANG13T/SatIntel/osint/export.go:365.18,366.51 1 0
+github.com/ANG13T/SatIntel/osint/export.go:367.10,368.54 1 0
+github.com/ANG13T/SatIntel/osint/export.go:373.78,375.16 2 0
+github.com/ANG13T/SatIntel/osint/export.go:375.16,377.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:378.2,391.50 6 0
+github.com/ANG13T/SatIntel/osint/export.go:391.50,393.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:394.2,394.46 1 0
+github.com/ANG13T/SatIntel/osint/export.go:394.46,396.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:399.2,399.49 1 0
+github.com/ANG13T/SatIntel/osint/export.go:399.49,401.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:404.2,409.50 2 0
+github.com/ANG13T/SatIntel/osint/export.go:409.50,411.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:414.2,414.35 1 0
+github.com/ANG13T/SatIntel/osint/export.go:414.35,428.43 2 0
+github.com/ANG13T/SatIntel/osint/export.go:428.43,430.4 1 0
+github.com/ANG13T/SatIntel/osint/export.go:433.2,433.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:437.79,450.16 3 0
+github.com/ANG13T/SatIntel/osint/export.go:450.16,452.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:454.2,454.63 1 0
+github.com/ANG13T/SatIntel/osint/export.go:454.63,456.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:458.2,458.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:462.79,477.35 11 0
+github.com/ANG13T/SatIntel/osint/export.go:477.35,485.3 4 0
+github.com/ANG13T/SatIntel/osint/export.go:487.2,489.79 2 0
+github.com/ANG13T/SatIntel/osint/export.go:489.79,491.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:493.2,493.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:497.89,498.16 1 0
+github.com/ANG13T/SatIntel/osint/export.go:499.17,500.52 1 0
+github.com/ANG13T/SatIntel/osint/export.go:501.18,502.53 1 0
+github.com/ANG13T/SatIntel/osint/export.go:503.18,504.53 1 0
+github.com/ANG13T/SatIntel/osint/export.go:505.10,506.54 1 0
+github.com/ANG13T/SatIntel/osint/export.go:511.71,513.16 2 0
+github.com/ANG13T/SatIntel/osint/export.go:513.16,515.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:516.2,527.50 6 0
+github.com/ANG13T/SatIntel/osint/export.go:527.50,529.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:530.2,530.46 1 0
+github.com/ANG13T/SatIntel/osint/export.go:530.46,532.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:535.2,535.49 1 0
+github.com/ANG13T/SatIntel/osint/export.go:535.49,537.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:540.2,544.49 2 0
+github.com/ANG13T/SatIntel/osint/export.go:544.49,546.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:549.2,549.37 1 0
+github.com/ANG13T/SatIntel/osint/export.go:549.37,559.43 2 0
+github.com/ANG13T/SatIntel/osint/export.go:559.43,561.4 1 0
+github.com/ANG13T/SatIntel/osint/export.go:564.2,564.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:568.72,579.16 3 0
+github.com/ANG13T/SatIntel/osint/export.go:579.16,581.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:583.2,583.63 1 0
+github.com/ANG13T/SatIntel/osint/export.go:583.63,585.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:587.2,587.12 1 0
+github.com/ANG13T/SatIntel/osint/export.go:591.72,604.37 9 0
+github.com/ANG13T/SatIntel/osint/export.go:604.37,612.3 7 0
+github.com/ANG13T/SatIntel/osint/export.go:614.2,616.79 2 0
+github.com/ANG13T/SatIntel/osint/export.go:616.79,618.3 1 0
+github.com/ANG13T/SatIntel/osint/export.go:620.2,620.12 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:32.32,34.16 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:34.16,37.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:38.2,40.51 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:44.51,48.16 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:48.16,49.25 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:49.25,52.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:53.3,53.67 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:56.2,57.61 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:57.61,59.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:61.2,61.37 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:65.57,73.16 4 0
+github.com/ANG13T/SatIntel/osint/favorites.go:73.16,75.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:77.2,77.64 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:77.64,79.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:81.2,81.12 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:85.70,87.16 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:87.16,89.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:92.2,92.32 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:92.32,93.29 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:93.29,95.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:98.2,107.33 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:111.43,113.16 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:113.16,115.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:117.2,119.32 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:119.32,120.29 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:120.29,122.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:122.9,124.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:127.2,127.12 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:127.12,129.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:131.2,131.40 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:135.47,137.16 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:137.16,139.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:141.2,141.32 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:141.32,142.29 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:142.29,144.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:147.2,147.19 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:151.35,153.16 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:153.16,156.3 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:158.2,158.25 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:158.25,162.3 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:164.2,165.32 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:165.32,167.24 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:167.24,169.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:170.3,170.27 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:170.27,172.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:173.3,174.38 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:177.2,186.16 4 0
+github.com/ANG13T/SatIntel/osint/favorites.go:186.16,189.3 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:191.2,191.27 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:191.27,194.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:196.2,197.73 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:201.24,203.16 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:203.16,206.3 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:208.2,208.25 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:208.25,211.3 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:213.2,226.16 4 0
+github.com/ANG13T/SatIntel/osint/favorites.go:226.16,228.3 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:230.2,230.13 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:231.9,234.33 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:234.33,236.25 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:236.25,238.5 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:239.4,239.28 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:239.28,241.5 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:242.4,243.28 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:243.28,245.5 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:247.3,247.39 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:249.9,251.33 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:251.33,253.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:254.3,262.48 4 0
+github.com/ANG13T/SatIntel/osint/favorites.go:262.48,264.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:266.3,267.58 2 0
+github.com/ANG13T/SatIntel/osint/favorites.go:267.58,269.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:269.9,271.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:273.9,281.17 3 0
+github.com/ANG13T/SatIntel/osint/favorites.go:281.17,283.4 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:285.3,285.59 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:285.59,286.63 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:286.63,288.5 1 0
+github.com/ANG13T/SatIntel/osint/favorites.go:288.10,290.5 1 0
+github.com/ANG13T/SatIntel/osint/orbitalelement.go:11.23,17.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalelement.go:17.20,20.19 2 0
+github.com/ANG13T/SatIntel/osint/orbitalelement.go:20.19,22.4 1 0
+github.com/ANG13T/SatIntel/osint/orbitalelement.go:24.3,24.47 1 0
+github.com/ANG13T/SatIntel/osint/orbitalelement.go:26.8,26.27 1 0
+github.com/ANG13T/SatIntel/osint/orbitalelement.go:26.27,31.3 4 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:17.26,23.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:23.20,25.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:25.8,25.27 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:25.27,27.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:31.28,33.27 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:33.27,36.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:37.2,41.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:41.20,44.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:45.2,49.21 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:49.21,52.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:53.2,57.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:57.20,60.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:61.2,65.16 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:65.16,68.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:69.2,73.15 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:73.15,76.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:79.2,89.76 9 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:89.76,92.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:94.2,98.16 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:98.16,101.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:102.2,106.16 4 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:106.16,109.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:111.2,120.26 8 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:120.26,125.36 4 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:125.36,127.4 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:128.8,130.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:133.2,139.61 3 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:139.61,142.17 3 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:142.17,143.73 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:143.73,145.5 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:145.10,147.5 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:153.27,155.27 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:155.27,158.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:159.2,163.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:163.20,166.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:167.2,171.21 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:171.21,174.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:175.2,179.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:179.20,182.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:183.2,187.16 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:187.16,190.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:191.2,195.21 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:195.21,198.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:201.2,211.76 9 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:211.76,214.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:216.2,218.16 3 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:218.16,221.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:222.2,226.16 4 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:226.16,229.3 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:231.2,240.26 8 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:240.26,245.36 4 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:245.36,247.4 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:248.8,250.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:253.2,259.61 3 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:259.61,262.17 3 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:262.17,263.72 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:263.72,265.5 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:265.10,267.5 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:273.50,278.20 5 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:278.20,281.19 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:281.19,283.4 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:285.3,285.75 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:287.8,287.27 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:287.27,292.3 4 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:294.2,294.33 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:350.45,352.29 2 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:352.29,353.65 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:353.65,355.4 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:357.2,357.24 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:361.44,376.10 15 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:376.10,378.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:378.8,380.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:384.48,395.10 11 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:395.10,397.3 1 0
+github.com/ANG13T/SatIntel/osint/orbitalprediction.go:397.8,399.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:25.36,34.16 7 0
+github.com/ANG13T/SatIntel/osint/osint.go:34.16,37.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:39.2,44.16 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:44.16,47.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:48.2,50.38 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:50.38,53.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:55.2,57.20 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:62.76,67.16 4 0
+github.com/ANG13T/SatIntel/osint/osint.go:67.16,70.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:72.2,73.16 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:73.16,76.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:77.2,79.38 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:79.38,82.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:84.2,85.16 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:85.16,88.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:89.2,90.26 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:94.38,97.46 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:97.46,99.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:100.2,100.27 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:104.48,106.16 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:106.16,109.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:111.2,113.16 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:113.16,116.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:118.2,122.21 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:122.21,125.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:125.8,127.24 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:127.24,130.4 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:133.3,134.24 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:134.24,136.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:137.3,137.26 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:137.26,139.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:141.3,141.14 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:141.14,143.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:145.3,145.27 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:145.27,147.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:149.3,150.46 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:153.2,153.39 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:153.39,156.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:157.2,157.39 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:157.39,160.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:162.2,169.50 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:169.50,171.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:171.8,171.111 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:171.111,173.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:175.2,175.19 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:175.19,179.53 4 0
+github.com/ANG13T/SatIntel/osint/osint.go:179.53,181.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:182.3,182.9 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:185.2,185.15 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:190.102,195.19 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:195.19,197.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:198.2,198.22 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:198.22,200.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:201.2,201.22 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:201.22,203.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:206.2,210.22 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:210.22,213.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:213.8,213.25 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:213.25,216.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:216.8,218.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:220.2,221.32 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:225.78,226.22 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:226.22,228.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:229.2,231.27 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:231.27,232.66 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:232.66,234.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:236.2,236.17 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:240.56,246.6 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:246.6,263.17 4 0
+github.com/ANG13T/SatIntel/osint/osint.go:263.17,265.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:267.3,267.14 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:268.10,275.18 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:275.18,277.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:279.10,286.18 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:286.18,288.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:290.10,304.18 4 0
+github.com/ANG13T/SatIntel/osint/osint.go:304.18,306.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:308.10,315.18 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:315.18,317.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:319.10,324.68 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:326.10,327.54 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:331.3,331.80 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:331.80,333.24 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:333.24,335.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:336.4,336.21 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:336.21,338.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:339.4,339.24 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:339.24,341.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:342.4,342.24 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:342.24,344.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:345.4,345.17 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:352.31,366.16 4 0
+github.com/ANG13T/SatIntel/osint/osint.go:366.16,368.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:370.2,370.21 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:370.21,373.19 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:373.19,376.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:377.3,377.12 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:378.8,378.28 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:378.28,381.3 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:384.2,385.16 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:385.16,388.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:391.2,398.6 6 0
+github.com/ANG13T/SatIntel/osint/osint.go:398.6,403.52 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:403.52,409.18 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:409.18,412.5 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:414.4,415.69 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:415.69,419.5 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:422.4,428.37 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:428.37,430.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:431.4,431.39 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:431.39,433.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:433.10,435.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:436.9,436.30 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:436.30,440.37 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:440.37,442.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:443.4,443.39 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:443.39,445.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:445.10,447.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:448.9,454.18 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:454.18,457.5 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:459.4,459.62 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:459.62,463.5 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:464.4,464.18 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:467.3,467.21 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:467.21,471.4 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:474.3,475.28 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:475.28,477.25 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:477.25,479.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:480.4,480.29 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:480.29,482.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:483.4,483.41 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:487.3,489.23 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:489.23,491.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:491.9,493.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:495.3,495.15 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:495.15,497.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:498.3,499.18 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:499.18,501.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:502.3,505.41 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:505.41,507.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:508.3,508.43 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:508.43,510.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:510.9,512.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:514.3,521.17 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:521.17,524.4 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:527.3,527.27 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:527.27,530.12 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:533.3,534.15 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:534.15,536.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:538.3,538.56 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:538.56,546.14 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:546.14,553.62 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:553.62,554.133 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:554.133,556.7 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:556.12,558.7 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:562.4,562.17 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:565.3,566.40 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:566.40,569.12 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:572.3,573.18 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:573.18,575.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:576.3,578.26 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:578.26,581.23 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:581.23,583.5 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:585.4,585.12 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:588.3,588.67 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:588.67,594.12 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:598.3,598.12 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:603.54,607.2 3 1
+github.com/ANG13T/SatIntel/osint/osint.go:611.35,616.16 5 0
+github.com/ANG13T/SatIntel/osint/osint.go:616.16,619.3 2 0
+github.com/ANG13T/SatIntel/osint/osint.go:619.8,620.17 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:620.17,624.4 3 0
+github.com/ANG13T/SatIntel/osint/osint.go:624.9,624.38 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:624.38,626.4 1 0
+github.com/ANG13T/SatIntel/osint/osint.go:626.9,629.4 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:23.42,32.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:35.27,36.15 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:36.15,38.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:39.2,41.12 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:41.12,45.7 3 0
+github.com/ANG13T/SatIntel/osint/progress.go:45.7,46.11 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:47.22,49.11 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:50.20,52.43 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:59.26,60.16 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:60.16,62.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:63.2,66.19 4 0
+github.com/ANG13T/SatIntel/osint/progress.go:70.49,72.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:84.61,92.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:95.44,96.18 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:96.18,98.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:99.2,100.27 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:100.27,102.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:103.2,103.13 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:107.36,108.18 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:108.18,110.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:111.2,112.27 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:112.27,114.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:115.2,115.13 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:119.44,121.27 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:121.27,123.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:124.2,124.13 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:128.33,140.2 5 0
+github.com/ANG13T/SatIntel/osint/progress.go:143.35,144.18 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:144.18,146.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:147.2,150.15 4 0
+github.com/ANG13T/SatIntel/osint/progress.go:154.35,156.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:159.21,161.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:164.55,168.2 3 0
+github.com/ANG13T/SatIntel/osint/progress.go:171.66,173.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:176.49,179.2 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:182.57,186.22 4 0
+github.com/ANG13T/SatIntel/osint/progress.go:186.22,188.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:192.35,194.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:197.50,200.42 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:200.42,202.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:202.8,202.46 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:202.46,204.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:204.8,204.53 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:204.53,206.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:207.2,207.66 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:211.53,213.2 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:216.24,218.16 2 0
+github.com/ANG13T/SatIntel/osint/progress.go:218.16,220.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:221.2,221.51 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:225.41,226.18 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:226.18,228.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:228.8,230.3 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:234.27,235.18 1 0
+github.com/ANG13T/SatIntel/osint/progress.go:235.18,237.3 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:17.39,23.20 5 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:23.20,26.19 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:26.19,28.4 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:30.3,30.36 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:32.8,32.27 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:32.27,37.3 4 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:41.32,46.20 5 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:46.20,49.3 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:50.2,54.21 5 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:54.21,57.3 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:58.2,62.20 5 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:62.20,65.3 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:68.2,76.46 7 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:76.46,79.3 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:81.2,85.16 5 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:85.16,88.3 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:89.2,93.16 4 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:93.16,96.3 2 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:98.2,109.38 9 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:109.38,111.3 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:114.2,120.61 3 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:120.61,123.17 3 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:123.17,124.74 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:124.74,126.5 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:126.10,128.5 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:134.20,135.2 0 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:138.54,145.10 7 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:145.10,147.3 1 0
+github.com/ANG13T/SatIntel/osint/satelliteposition.go:147.8,149.3 1 0
+github.com/ANG13T/SatIntel/osint/sgp4.go:45.92,50.40 3 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:50.40,52.3 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:54.2,54.37 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:54.37,56.3 1 0
+github.com/ANG13T/SatIntel/osint/sgp4.go:57.2,57.37 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:57.37,59.3 1 0
+github.com/ANG13T/SatIntel/osint/sgp4.go:62.2,92.8 13 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:97.108,99.2 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:103.138,106.16 2 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:106.16,108.3 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:111.2,155.8 20 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:159.137,160.30 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:160.30,162.3 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:163.2,163.19 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:163.19,165.3 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:167.2,170.34 3 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:170.34,172.17 2 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:172.17,174.4 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:175.3,176.42 2 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:179.2,179.23 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:184.130,186.2 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:189.176,191.2 1 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:194.41,204.2 9 1
+github.com/ANG13T/SatIntel/osint/sgp4.go:207.65,221.2 13 1
+github.com/ANG13T/SatIntel/osint/tle.go:36.61,42.23 5 1
+github.com/ANG13T/SatIntel/osint/tle.go:42.23,44.3 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:45.2,45.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:45.24,47.3 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:49.2,49.47 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:49.47,51.26 2 1
+github.com/ANG13T/SatIntel/osint/tle.go:51.26,54.4 2 1
+github.com/ANG13T/SatIntel/osint/tle.go:54.9,56.4 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:58.2,58.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:58.23,60.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:61.2,61.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:61.23,63.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:64.2,64.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:64.23,66.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:67.2,67.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:67.23,69.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:70.2,70.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:70.23,72.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:73.2,73.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:73.23,75.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:76.2,76.23 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:76.23,78.25 2 1
+github.com/ANG13T/SatIntel/osint/tle.go:78.25,81.4 2 1
+github.com/ANG13T/SatIntel/osint/tle.go:81.9,81.32 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:81.32,83.4 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:86.2,86.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:86.24,88.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:89.2,89.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:89.24,91.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:92.2,92.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:92.24,94.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:95.2,95.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:95.24,97.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:98.2,98.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:98.24,100.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:101.2,101.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:101.24,103.3 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:104.2,104.24 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:104.24,106.31 2 1
+github.com/ANG13T/SatIntel/osint/tle.go:106.31,108.32 2 1
+github.com/ANG13T/SatIntel/osint/tle.go:108.32,110.5 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:111.9,113.4 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:114.3,114.29 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:114.29,116.4 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:119.2,119.12 1 1
+github.com/ANG13T/SatIntel/osint/tle.go:123.24,154.61 24 0
+github.com/ANG13T/SatIntel/osint/tle.go:154.61,157.17 3 0
+github.com/ANG13T/SatIntel/osint/tle.go:157.17,158.59 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:158.59,160.5 1 0
+github.com/ANG13T/SatIntel/osint/tle.go:160.10,162.5 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:15.18,21.20 5 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:21.20,23.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:23.8,23.27 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:23.27,25.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:30.42,35.16 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:35.16,37.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:40.2,40.36 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:40.36,42.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:45.2,55.44 3 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:55.44,56.65 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:56.65,58.4 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:62.2,62.22 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:62.22,64.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:67.2,70.41 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:70.41,72.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:78.2,78.12 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:82.20,88.47 4 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:88.47,91.3 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:94.2,98.16 3 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:98.16,101.3 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:104.2,104.22 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:104.22,107.3 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:109.2,111.16 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:111.16,114.3 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:115.2,122.21 6 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:122.21,125.3 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:127.2,127.28 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:127.28,130.3 2 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:132.2,135.16 3 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:135.16,140.3 4 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:140.8,144.3 3 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:147.2,151.50 4 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:151.50,153.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:153.8,153.120 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:153.120,155.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:157.2,157.19 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:157.19,161.53 4 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:161.53,163.4 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:164.3,164.9 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:167.2,167.18 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:171.23,188.19 14 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:188.19,190.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:192.2,199.50 5 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:199.50,201.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:201.8,201.120 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:201.120,203.3 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:205.2,205.19 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:205.19,209.53 4 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:209.53,211.4 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:212.3,212.9 1 0
+github.com/ANG13T/SatIntel/osint/tleparser.go:215.2,215.18 1 0
diff --git a/go.mod b/go.mod
index 02e1c36..4e7cbb0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,33 +1,22 @@
module github.com/ANG13T/SatIntel
-go 1.20
+go 1.24.0
+
+toolchain go1.24.5
+
+require (
+ github.com/TwiN/go-color v1.4.0
+ github.com/iskaa02/qalam v0.3.0
+ github.com/manifoldco/promptui v0.9.0
+)
require (
- github.com/TwiN/go-color v1.4.0 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
- github.com/fsnotify/fsnotify v1.6.0 // indirect
- github.com/hashicorp/hcl v1.0.0 // indirect
- github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/iskaa02/qalam v0.3.0 // indirect
+ github.com/joshuaferrara/go-satellite v0.0.0-20220611180459-512638c64e5b // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
- github.com/magiconair/properties v1.8.7 // indirect
- github.com/manifoldco/promptui v0.9.0 // indirect
- github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mazznoer/colorgrad v0.8.1 // indirect
github.com/mazznoer/csscolorparser v0.1.0 // indirect
- github.com/mitchellh/mapstructure v1.5.0 // indirect
- github.com/olekukonko/tablewriter v0.0.5 // indirect
- github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
- github.com/spf13/afero v1.9.3 // indirect
- github.com/spf13/cast v1.5.0 // indirect
- github.com/spf13/cobra v1.7.0 // indirect
- github.com/spf13/jwalterweatherman v1.1.0 // indirect
- github.com/spf13/pflag v1.0.5 // indirect
- github.com/spf13/viper v1.15.0 // indirect
- github.com/subosito/gotenv v1.4.2 // indirect
- golang.org/x/sys v0.3.0 // indirect
- golang.org/x/text v0.5.0 // indirect
- gopkg.in/ini.v1 v1.67.0 // indirect
- gopkg.in/yaml.v3 v3.0.1 // indirect
+ golang.org/x/sys v0.39.0 // indirect
+ golang.org/x/term v0.38.0 // indirect
)
diff --git a/go.sum b/go.sum
index 26f50ba..667f2d1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,496 +1,32 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
-cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
-cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
-cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
-cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
-cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
-cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
-cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
-cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
-cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
-cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
-cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
-cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
-cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
-cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
-cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
-cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
-cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
-cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
-cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
-cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
-cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
-cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
-cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
-cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
-cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
-cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
-cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
-cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
-cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
-cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
-cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
-cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
-dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/TwiN/go-color v1.4.0 h1:fNbOwOrvup5oj934UragnW0B1WKaAkkB85q19Y7h4ng=
github.com/TwiN/go-color v1.4.0/go.mod h1:0QTVEPlu+AoCyTrho7bXbVkrCkVpdQr7YF7PYWEtSxM=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
-github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
-github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
-github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
-github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
-github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
-github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
-github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
-github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
-github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
-github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
-github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
-github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
-github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
-github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
-github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
-github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
-github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
-github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
-github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
-github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
-github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
-github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
-github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
-github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
-github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
-github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
-github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
-github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
-github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
-github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/iskaa02/qalam v0.3.0 h1:yA7+MXkXbkP0HRDpkODC3bgQG0e5mjsYOsawwYP2v5k=
github.com/iskaa02/qalam v0.3.0/go.mod h1:BRa4ht8cMjl27tNzhtuach90dBTma5seOzjpMGA5MY4=
-github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
-github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
-github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
-github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/joshuaferrara/go-satellite v0.0.0-20220611180459-512638c64e5b h1:JlltDRgni6FuoFwluvoZCrE6cmpojccO4WsqeYlFJLE=
+github.com/joshuaferrara/go-satellite v0.0.0-20220611180459-512638c64e5b/go.mod h1:msW2QeN9IsnRyvuK8OBAzBwn6DHwXpiAiqBk8dbLfrU=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
-github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
-github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
-github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
-github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mazznoer/colorgrad v0.8.1 h1:Bw/ks+KujOOg9E6YQvPqSqTLryiFnwliAH5VMZarSTI=
github.com/mazznoer/colorgrad v0.8.1/go.mod h1:xCjvoNkXHJIAPOUMSMrXkFdxTGQqk8zMYS3e5hSLghA=
github.com/mazznoer/csscolorparser v0.1.0 h1:xUf1uzU1r24JleIIb2Kz3bl7vATStxy53gm67yuPP+c=
github.com/mazznoer/csscolorparser v0.1.0/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic=
-github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
-github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
-github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v1.2.1-0.20160509182050-5437a97bf824/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20160516222431-c73e51675ad2/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
-github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
-github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
-github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
-github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
-github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
-github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
-github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
-github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
-github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
-github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
-github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
-github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
-github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
-github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
-github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
-github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
-github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
-github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
-github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
-go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
-go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
-go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
-golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
-golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
-golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
-golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
-golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
-golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
-golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
-golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
-golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
-golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
-golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
-golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
-golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
-golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
-golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
-golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
-golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
-golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
-golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
-golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
-golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
-golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
-golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
-golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
-golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
-golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
-golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
-google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
-google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
-google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
-google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
-google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
-google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
-google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
-google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
-google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
-google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
-google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
-google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
-google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
-google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
-google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
-google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
-google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
-google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
-google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
-google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
-google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
-google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
-google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
-google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
-google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
-google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
-google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
-google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
-google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
-google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
-google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
-google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
-google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
-google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
-google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
-google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
-google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
-google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
-google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
-google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
-gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
-gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
-gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
+golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
+golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
gopkg.in/yaml.v2 v2.0.0-20160301204022-a83829b6f129/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
-honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
-honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
-rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
-rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/main.go b/main.go
index f22ed36..9eb8d76 100644
--- a/main.go
+++ b/main.go
@@ -6,23 +6,182 @@ package main
import (
"bufio"
+ "encoding/json"
"fmt"
+ "io"
+ "net/http"
+ "net/http/cookiejar"
+ "net/url"
"os"
"strings"
"github.com/ANG13T/SatIntel/cli"
+ "golang.org/x/term"
)
-func setEnvironmentalVariable(envKey string) string {
- reader := bufio.NewReader(os.Stdin)
- fmt.Printf("%s: ", envKey)
- input, err := reader.ReadString('\n')
+// loadEnvFile reads environment variables from a .env file in the current directory.
+// It skips empty lines and comments, and handles quoted values.
+func loadEnvFile() error {
+ envPath := ".env"
+
+ if _, err := os.Stat(envPath); os.IsNotExist(err) {
+ return fmt.Errorf(".env file not found")
+ }
+ file, err := os.Open(envPath)
if err != nil {
- fmt.Println("Error reading input:", err)
- os.Exit(1)
+ return fmt.Errorf("failed to open .env file: %w", err)
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+
+ parts := strings.SplitN(line, "=", 2)
+ if len(parts) == 2 {
+ key := strings.TrimSpace(parts[0])
+ value := strings.TrimSpace(parts[1])
+ value = strings.Trim(value, "\"'")
+ os.Setenv(key, value)
+ }
+ }
+
+ if err := scanner.Err(); err != nil {
+ return fmt.Errorf("error reading .env file: %w", err)
+ }
+
+ return nil
+}
+
+// isPasswordField determines if the environment variable should have masked input.
+func isPasswordField(envKey string) bool {
+ passwordFields := []string{
+ "SPACE_TRACK_PASSWORD",
+ "N2YO_API_KEY",
+ "PASSWORD",
+ "API_KEY",
+ "SECRET",
+ "TOKEN",
+ }
+ envKeyUpper := strings.ToUpper(envKey)
+ for _, field := range passwordFields {
+ if strings.Contains(envKeyUpper, field) {
+ return true
+ }
+ }
+ return false
+}
+
+// readPassword reads a password from stdin and displays asterisks for each character typed.
+func readPassword() (string, error) {
+ fd := int(os.Stdin.Fd())
+
+ // Check if stdin is a terminal
+ if !term.IsTerminal(fd) {
+ // Fallback to regular input if not a terminal
+ reader := bufio.NewReader(os.Stdin)
+ input, err := reader.ReadString('\n')
+ if err != nil {
+ return "", err
+ }
+ return strings.TrimSpace(input), nil
+ }
+
+ // Save current terminal state and set to raw mode
+ oldState, err := term.MakeRaw(fd)
+ if err != nil {
+ return "", fmt.Errorf("failed to set raw terminal: %w", err)
+ }
+ defer term.Restore(fd, oldState)
+
+ var password []byte
+ var input [1]byte
+
+ for {
+ n, err := os.Stdin.Read(input[:])
+ if err != nil || n == 0 {
+ break
+ }
+
+ char := input[0]
+
+ // Handle Enter key (carriage return or newline)
+ if char == '\r' || char == '\n' {
+ fmt.Println()
+ break
+ }
+
+ // Handle Backspace/Delete (127 = DEL, 8 = BS)
+ if char == 127 || char == 8 {
+ if len(password) > 0 {
+ password = password[:len(password)-1]
+ // Move cursor back, print space, move cursor back again
+ fmt.Print("\b \b")
+ }
+ continue
+ }
+
+ // Handle Ctrl+C
+ if char == 3 {
+ fmt.Println()
+ os.Exit(1)
+ }
+
+ // Skip control characters except those we handle
+ if char < 32 {
+ continue
+ }
+
+ // Add character to password and print asterisk
+ password = append(password, char)
+ fmt.Print("*")
+ }
+
+ return string(password), nil
+}
+
+// setEnvironmentalVariable prompts the user to enter a value for the given environment variable.
+// It reads from stdin and sets the environment variable with the provided value.
+// Password fields display asterisks (*) for each character typed for security.
+func setEnvironmentalVariable(envKey string) string {
+ var input string
+ var err error
+
+ for {
+ fmt.Printf("%s: ", envKey)
+
+ if isPasswordField(envKey) {
+ input, err = readPassword()
+ if err != nil {
+ fmt.Println("Error reading password:", err)
+ os.Exit(1)
+ }
+ } else {
+ reader := bufio.NewReader(os.Stdin)
+ input, err = reader.ReadString('\n')
+ if err != nil {
+ fmt.Println("Error reading input:", err)
+ os.Exit(1)
+ }
+ input = strings.TrimSpace(input)
+ }
+
+ input = strings.TrimSpace(input)
+
+ // Validate format
+ if err := validateAPIKeyFormat(envKey, input); err != nil {
+ fmt.Printf(" [!] Validation error: %v\n", err)
+ fmt.Println("Please enter a valid value:")
+ continue
+ }
+
+ break
}
- input = strings.TrimSuffix(input, "\n")
if err := os.Setenv(envKey, input); err != nil {
fmt.Printf("Error setting environment variable %s: %v\n", envKey, err)
@@ -32,7 +191,8 @@ func setEnvironmentalVariable(envKey string) string {
return input
}
-
+// checkEnvironmentalVariable verifies if an environment variable exists.
+// If it doesn't exist, it prompts the user to provide a value.
func checkEnvironmentalVariable(envKey string) {
_, found := os.LookupEnv(envKey)
if !found {
@@ -40,11 +200,182 @@ func checkEnvironmentalVariable(envKey string) {
}
}
+// validateAPIKeyFormat validates the format of API keys and credentials.
+// Returns an error if the format is invalid.
+func validateAPIKeyFormat(envKey, value string) error {
+ value = strings.TrimSpace(value)
+
+ if value == "" {
+ return fmt.Errorf("%s cannot be empty", envKey)
+ }
+
+ // Check for reasonable length limits
+ if len(value) < 3 {
+ return fmt.Errorf("%s is too short (minimum 3 characters)", envKey)
+ }
+
+ if len(value) > 512 {
+ return fmt.Errorf("%s is too long (maximum 512 characters)", envKey)
+ }
+
+ // Space-Track username validation
+ if envKey == "SPACE_TRACK_USERNAME" {
+ // Username should not contain certain special characters
+ if strings.ContainsAny(value, "\n\r\t") {
+ return fmt.Errorf("username contains invalid characters")
+ }
+ }
+
+ // N2YO API key validation (typically alphanumeric, may contain hyphens)
+ if envKey == "N2YO_API_KEY" {
+ // N2YO API keys are typically alphanumeric with possible hyphens/underscores
+ // Allow alphanumeric, hyphens, underscores
+ hasAlphanumeric := false
+ for _, r := range value {
+ if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') {
+ hasAlphanumeric = true
+ } else if r == ' ' || r == '\n' || r == '\r' || r == '\t' {
+ return fmt.Errorf("API key contains invalid whitespace characters")
+ } else if r != '-' && r != '_' {
+ // Allow hyphens and underscores, but warn about other special chars
+ // (We'll be lenient here as API key formats can vary)
+ }
+ }
+ if !hasAlphanumeric {
+ return fmt.Errorf("API key must contain at least one alphanumeric character")
+ }
+ }
+
+ return nil
+}
+
+// validateCredentials validates all API credentials and tests connections.
+func validateCredentials() error {
+ username := os.Getenv("SPACE_TRACK_USERNAME")
+ password := os.Getenv("SPACE_TRACK_PASSWORD")
+ apiKey := os.Getenv("N2YO_API_KEY")
+
+ // Validate format
+ if err := validateAPIKeyFormat("SPACE_TRACK_USERNAME", username); err != nil {
+ return fmt.Errorf("Space-Track username validation failed: %w", err)
+ }
+
+ if err := validateAPIKeyFormat("SPACE_TRACK_PASSWORD", password); err != nil {
+ return fmt.Errorf("Space-Track password validation failed: %w", err)
+ }
+
+ if err := validateAPIKeyFormat("N2YO_API_KEY", apiKey); err != nil {
+ return fmt.Errorf("N2YO API key validation failed: %w", err)
+ }
+
+ // Test Space-Track connection
+ fmt.Println("Validating Space-Track credentials...")
+ client, err := testSpaceTrackConnection(username, password)
+ if err != nil {
+ return fmt.Errorf("Space-Track connection test failed: %w", err)
+ }
+ _ = client // Client is validated, can be used later if needed
+
+ // Test N2YO API connection
+ fmt.Println("Validating N2YO API key...")
+ if err := testN2YOConnection(apiKey); err != nil {
+ return fmt.Errorf("N2YO API connection test failed: %w", err)
+ }
+
+ fmt.Println("All credentials validated successfully!")
+ return nil
+}
+
+// testSpaceTrackConnection tests the Space-Track API connection.
+func testSpaceTrackConnection(username, password string) (*http.Client, error) {
+ // Import osint package functions - we'll need to make Login accessible or create a test function
+ // For now, we'll create a minimal test here
+ vals := url.Values{}
+ vals.Add("identity", username)
+ vals.Add("password", password)
+
+ jar, err := cookiejar.New(nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create cookie jar: %w", err)
+ }
+
+ client := &http.Client{
+ Jar: jar,
+ }
+
+ resp, err := client.PostForm("https://www.space-track.org/ajaxauth/login", vals)
+ if err != nil {
+ return nil, fmt.Errorf("connection error: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("authentication failed (status: %d)", resp.StatusCode)
+ }
+
+ return client, nil
+}
+
+// testN2YOConnection tests the N2YO API connection with a simple request.
+func testN2YOConnection(apiKey string) error {
+ // Test with a simple request - get positions for ISS (NORAD ID 25544)
+ // Using minimal parameters to reduce API usage
+ testURL := fmt.Sprintf("https://api.n2yo.com/rest/v1/satellite/positions/25544/0/0/0/1/&apiKey=%s", apiKey)
+
+ resp, err := http.Get(testURL)
+ if err != nil {
+ return fmt.Errorf("connection error: %w", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ // Try to read error message
+ body, _ := io.ReadAll(resp.Body)
+ return fmt.Errorf("API request failed (status: %d): %s", resp.StatusCode, string(body))
+ }
+
+ // Verify response is valid JSON
+ var result map[string]interface{}
+ if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
+ return fmt.Errorf("invalid API response format: %w", err)
+ }
+
+ // Check for error in response
+ if info, ok := result["info"].(map[string]interface{}); ok {
+ if errMsg, ok := info["error"].(string); ok && errMsg != "" {
+ return fmt.Errorf("API error: %s", errMsg)
+ }
+ }
+
+ return nil
+}
func main() {
+ err := loadEnvFile()
+ if err != nil {
+ if err.Error() == ".env file not found" {
+ fmt.Println("Note: .env file not found. Please provide credentials:")
+ } else {
+ fmt.Printf("Warning: Error loading .env file: %v\n", err)
+ fmt.Println("Please provide credentials manually:")
+ }
+ fmt.Println()
+ } else {
+ fmt.Println("Loaded credentials from .env file")
+ }
+
checkEnvironmentalVariable("SPACE_TRACK_USERNAME")
checkEnvironmentalVariable("SPACE_TRACK_PASSWORD")
checkEnvironmentalVariable("N2YO_API_KEY")
+ // Validate credentials format and test connections
+ fmt.Println("\nValidating API credentials...")
+ if err := validateCredentials(); err != nil {
+ fmt.Printf("Warning: Credential validation failed: %v\n", err)
+ fmt.Println("You may experience issues when using API features.")
+ fmt.Println("Press Enter to continue anyway, or Ctrl+C to exit and fix credentials...")
+ bufio.NewReader(os.Stdin).ReadBytes('\n')
+ }
+
cli.SatIntel()
}
diff --git a/main_apivalidation_test.go b/main_apivalidation_test.go
new file mode 100644
index 0000000..01e0f66
--- /dev/null
+++ b/main_apivalidation_test.go
@@ -0,0 +1,375 @@
+package main
+
+// NOTE: This test file contains fake test credentials (e.g., "fake_test_password", "FAKE-TEST-KEY")
+// These are NOT real secrets and are used only for testing purposes.
+
+import (
+ "os"
+ "strings"
+ "testing"
+)
+
+func TestValidateAPIKeyFormat(t *testing.T) {
+ tests := []struct {
+ name string
+ envKey string
+ value string
+ expectError bool
+ errorMsg string
+ }{
+ // Valid cases
+ {
+ name: "Valid Space-Track username",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "testuser123",
+ expectError: false,
+ },
+ {
+ name: "Valid Space-Track password",
+ envKey: "SPACE_TRACK_PASSWORD",
+ value: "fake_test_password_123", // Test value only
+ expectError: false,
+ },
+ {
+ name: "Valid N2YO API key",
+ envKey: "N2YO_API_KEY",
+ value: "FAKE-TEST-KEY-12345-67890", // Test value only
+ expectError: false,
+ },
+ {
+ name: "Valid N2YO API key with underscores",
+ envKey: "N2YO_API_KEY",
+ value: "test_key_123",
+ expectError: false,
+ },
+ {
+ name: "Valid long credentials",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: strings.Repeat("a", 100),
+ expectError: false,
+ },
+ // Empty values
+ {
+ name: "Empty username",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "",
+ expectError: true,
+ errorMsg: "cannot be empty",
+ },
+ {
+ name: "Whitespace only username",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: " ",
+ expectError: true,
+ errorMsg: "cannot be empty",
+ },
+ {
+ name: "Empty password",
+ envKey: "SPACE_TRACK_PASSWORD",
+ value: "",
+ expectError: true,
+ errorMsg: "cannot be empty",
+ },
+ {
+ name: "Empty API key",
+ envKey: "N2YO_API_KEY",
+ value: "",
+ expectError: true,
+ errorMsg: "cannot be empty",
+ },
+ // Too short
+ {
+ name: "Username too short",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "ab",
+ expectError: true,
+ errorMsg: "too short",
+ },
+ {
+ name: "Password too short",
+ envKey: "SPACE_TRACK_PASSWORD",
+ value: "12",
+ expectError: true,
+ errorMsg: "too short",
+ },
+ {
+ name: "API key too short",
+ envKey: "N2YO_API_KEY",
+ value: "ab",
+ expectError: true,
+ errorMsg: "too short",
+ },
+ // Too long
+ {
+ name: "Username too long",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: strings.Repeat("a", 513),
+ expectError: true,
+ errorMsg: "too long",
+ },
+ {
+ name: "Password too long",
+ envKey: "SPACE_TRACK_PASSWORD",
+ value: strings.Repeat("a", 513),
+ expectError: true,
+ errorMsg: "too long",
+ },
+ {
+ name: "API key too long",
+ envKey: "N2YO_API_KEY",
+ value: strings.Repeat("a", 513),
+ expectError: true,
+ errorMsg: "too long",
+ },
+ // Invalid characters
+ {
+ name: "Username with newline",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "user\nname",
+ expectError: true,
+ errorMsg: "invalid characters",
+ },
+ {
+ name: "Username with carriage return",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "user\rname",
+ expectError: true,
+ errorMsg: "invalid characters",
+ },
+ {
+ name: "Username with tab",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "user\tname",
+ expectError: true,
+ errorMsg: "invalid characters",
+ },
+ {
+ name: "API key with space",
+ envKey: "N2YO_API_KEY",
+ value: "key with spaces",
+ expectError: true,
+ errorMsg: "invalid whitespace",
+ },
+ {
+ name: "API key with newline",
+ envKey: "N2YO_API_KEY",
+ value: "key\nwith\nnewlines",
+ expectError: true,
+ errorMsg: "invalid whitespace",
+ },
+ {
+ name: "API key with only special chars",
+ envKey: "N2YO_API_KEY",
+ value: "---",
+ expectError: true,
+ errorMsg: "must contain at least one alphanumeric",
+ },
+ // Edge cases
+ {
+ name: "Minimum length username",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: "abc",
+ expectError: false,
+ },
+ {
+ name: "Maximum length username",
+ envKey: "SPACE_TRACK_USERNAME",
+ value: strings.Repeat("a", 512),
+ expectError: false,
+ },
+ {
+ name: "API key with mixed case",
+ envKey: "N2YO_API_KEY",
+ value: "FAKE-TEST-KEY-ABC-XYZ", // Test value only
+ expectError: false,
+ },
+ {
+ name: "API key with numbers only",
+ envKey: "N2YO_API_KEY",
+ value: "123456789",
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := validateAPIKeyFormat(tt.envKey, tt.value)
+ if tt.expectError {
+ if err == nil {
+ t.Errorf("validateAPIKeyFormat(%q, %q) expected error, got nil", tt.envKey, tt.value)
+ } else if tt.errorMsg != "" && !strings.Contains(err.Error(), tt.errorMsg) {
+ t.Errorf("validateAPIKeyFormat(%q, %q) error = %v, want error containing %q", tt.envKey, tt.value, err, tt.errorMsg)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("validateAPIKeyFormat(%q, %q) unexpected error: %v", tt.envKey, tt.value, err)
+ }
+ }
+ })
+ }
+}
+
+func TestValidateAPIKeyFormatUnknownKey(t *testing.T) {
+ // Test with unknown environment variable key
+ err := validateAPIKeyFormat("UNKNOWN_KEY", "testvalue")
+ if err != nil {
+ t.Errorf("validateAPIKeyFormat with unknown key should only check basic format, got error: %v", err)
+ }
+}
+
+func TestValidateCredentialsFormatOnly(t *testing.T) {
+ // Test format validation without actual API calls
+ // This requires mocking or skipping connection tests
+
+ // Save original values
+ origUsername := os.Getenv("SPACE_TRACK_USERNAME")
+ origPassword := os.Getenv("SPACE_TRACK_PASSWORD")
+ origAPIKey := os.Getenv("N2YO_API_KEY")
+
+ // Set test values
+ os.Setenv("SPACE_TRACK_USERNAME", "testuser")
+ os.Setenv("SPACE_TRACK_PASSWORD", "fake_test_password") // Test value only
+ os.Setenv("N2YO_API_KEY", "fake_test_key_123") // Test value only
+
+ // Test format validation (we'll skip connection tests in unit tests)
+ username := os.Getenv("SPACE_TRACK_USERNAME")
+ password := os.Getenv("SPACE_TRACK_PASSWORD")
+ apiKey := os.Getenv("N2YO_API_KEY")
+
+ if err := validateAPIKeyFormat("SPACE_TRACK_USERNAME", username); err != nil {
+ t.Errorf("Username format validation failed: %v", err)
+ }
+
+ if err := validateAPIKeyFormat("SPACE_TRACK_PASSWORD", password); err != nil {
+ t.Errorf("Password format validation failed: %v", err)
+ }
+
+ if err := validateAPIKeyFormat("N2YO_API_KEY", apiKey); err != nil {
+ t.Errorf("API key format validation failed: %v", err)
+ }
+
+ // Restore original values
+ if origUsername != "" {
+ os.Setenv("SPACE_TRACK_USERNAME", origUsername)
+ } else {
+ os.Unsetenv("SPACE_TRACK_USERNAME")
+ }
+ if origPassword != "" {
+ os.Setenv("SPACE_TRACK_PASSWORD", origPassword)
+ } else {
+ os.Unsetenv("SPACE_TRACK_PASSWORD")
+ }
+ if origAPIKey != "" {
+ os.Setenv("N2YO_API_KEY", origAPIKey)
+ } else {
+ os.Unsetenv("N2YO_API_KEY")
+ }
+}
+
+// TestValidateCredentialsFormatOnlyWithInvalidInput tests format validation with invalid inputs
+func TestValidateCredentialsFormatOnlyWithInvalidInput(t *testing.T) {
+ // Save original values
+ origUsername := os.Getenv("SPACE_TRACK_USERNAME")
+ origPassword := os.Getenv("SPACE_TRACK_PASSWORD")
+ origAPIKey := os.Getenv("N2YO_API_KEY")
+
+ defer func() {
+ if origUsername != "" {
+ os.Setenv("SPACE_TRACK_USERNAME", origUsername)
+ } else {
+ os.Unsetenv("SPACE_TRACK_USERNAME")
+ }
+ if origPassword != "" {
+ os.Setenv("SPACE_TRACK_PASSWORD", origPassword)
+ } else {
+ os.Unsetenv("SPACE_TRACK_PASSWORD")
+ }
+ if origAPIKey != "" {
+ os.Setenv("N2YO_API_KEY", origAPIKey)
+ } else {
+ os.Unsetenv("N2YO_API_KEY")
+ }
+ }()
+
+ // Test with invalid format credentials
+ os.Setenv("SPACE_TRACK_USERNAME", "ab") // Too short
+ os.Setenv("SPACE_TRACK_PASSWORD", "fake_test_password") // Test value only
+ os.Setenv("N2YO_API_KEY", "fake_test_key_123") // Test value only
+
+ username := os.Getenv("SPACE_TRACK_USERNAME")
+ if err := validateAPIKeyFormat("SPACE_TRACK_USERNAME", username); err == nil {
+ t.Error("Expected error for too short username, got nil")
+ }
+
+ // Test with valid format
+ os.Setenv("SPACE_TRACK_USERNAME", "testuser")
+ if err := validateAPIKeyFormat("SPACE_TRACK_USERNAME", "testuser"); err != nil {
+ t.Errorf("Expected no error for valid username, got: %v", err)
+ }
+}
+
+// TestValidateAPIKeyFormatPasswordField tests password field validation
+func TestValidateAPIKeyFormatPasswordField(t *testing.T) {
+ tests := []struct {
+ name string
+ value string
+ expectError bool
+ }{
+ {
+ name: "Valid password",
+ value: "fake_test_pass_123", // Test value only
+ expectError: false,
+ },
+ {
+ name: "Password with special characters",
+ value: "Pass@123!",
+ expectError: false, // Passwords can have special chars
+ },
+ {
+ name: "Password too short",
+ value: "ab",
+ expectError: true,
+ },
+ {
+ name: "Password too long",
+ value: strings.Repeat("a", 513),
+ expectError: true,
+ },
+ {
+ name: "Empty password",
+ value: "",
+ expectError: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := validateAPIKeyFormat("SPACE_TRACK_PASSWORD", tt.value)
+ if tt.expectError && err == nil {
+ t.Errorf("Expected error for %q, got nil", tt.value)
+ } else if !tt.expectError && err != nil {
+ t.Errorf("Unexpected error for %q: %v", tt.value, err)
+ }
+ })
+ }
+}
+
+// Benchmark tests
+func BenchmarkValidateAPIKeyFormat(b *testing.B) {
+ testCases := []struct {
+ envKey string
+ value string
+ }{
+ {"SPACE_TRACK_USERNAME", "testuser123"},
+ {"SPACE_TRACK_PASSWORD", "fake_test_password_123"}, // Test value only
+ {"N2YO_API_KEY", "FAKE-TEST-KEY-12345"}, // Test value only
+ {"SPACE_TRACK_USERNAME", strings.Repeat("a", 100)},
+ {"N2YO_API_KEY", "fake_test_key_123"}, // Test value only
+ }
+
+ for i := 0; i < b.N; i++ {
+ for _, tc := range testCases {
+ validateAPIKeyFormat(tc.envKey, tc.value)
+ }
+ }
+}
diff --git a/main_test.go b/main_test.go
new file mode 100644
index 0000000..a8103dc
--- /dev/null
+++ b/main_test.go
@@ -0,0 +1,423 @@
+package main
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestIsPasswordField(t *testing.T) {
+ tests := []struct {
+ name string
+ envKey string
+ expected bool
+ }{
+ {
+ name: "SPACE_TRACK_PASSWORD should be masked with asterisks",
+ envKey: "SPACE_TRACK_PASSWORD",
+ expected: true,
+ },
+ {
+ name: "N2YO_API_KEY should be masked with asterisks",
+ envKey: "N2YO_API_KEY",
+ expected: true,
+ },
+ {
+ name: "SPACE_TRACK_USERNAME should not be masked",
+ envKey: "SPACE_TRACK_USERNAME",
+ expected: false,
+ },
+ {
+ name: "Field containing PASSWORD should be masked with asterisks",
+ envKey: "MY_PASSWORD",
+ expected: true,
+ },
+ {
+ name: "Field containing API_KEY should be masked with asterisks",
+ envKey: "SOME_API_KEY",
+ expected: true,
+ },
+ {
+ name: "Field containing SECRET should be masked with asterisks",
+ envKey: "MY_SECRET",
+ expected: true,
+ },
+ {
+ name: "Field containing TOKEN should be masked with asterisks",
+ envKey: "ACCESS_TOKEN",
+ expected: true,
+ },
+ {
+ name: "Lowercase password field should be masked with asterisks",
+ envKey: "my_password",
+ expected: true,
+ },
+ {
+ name: "Mixed case password field should be masked with asterisks",
+ envKey: "My_Password_Field",
+ expected: true,
+ },
+ {
+ name: "Regular environment variable should not be masked",
+ envKey: "DATABASE_HOST",
+ expected: false,
+ },
+ {
+ name: "Empty string should not be masked",
+ envKey: "",
+ expected: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := isPasswordField(tt.envKey)
+ if result != tt.expected {
+ t.Errorf("isPasswordField(%q) = %v, want %v", tt.envKey, result, tt.expected)
+ }
+ })
+ }
+}
+
+func TestLoadEnvFile(t *testing.T) {
+ // Create a temporary directory for test files
+ tmpDir := t.TempDir()
+
+ tests := []struct {
+ name string
+ envContent string
+ expectError bool
+ checkEnv func(t *testing.T) // Function to check environment variables
+ }{
+ {
+ name: "Valid .env file with all variables",
+ envContent: `SPACE_TRACK_USERNAME=testuser
+SPACE_TRACK_PASSWORD=fake_test_password
+N2YO_API_KEY=fake_test_key
+`,
+ expectError: false,
+ checkEnv: func(t *testing.T) {
+ if val := os.Getenv("SPACE_TRACK_USERNAME"); val != "testuser" {
+ t.Errorf("SPACE_TRACK_USERNAME = %q, want %q", val, "testuser")
+ }
+ if val := os.Getenv("SPACE_TRACK_PASSWORD"); val != "fake_test_password" {
+ t.Errorf("SPACE_TRACK_PASSWORD = %q, want %q", val, "fake_test_password")
+ }
+ if val := os.Getenv("N2YO_API_KEY"); val != "fake_test_key" {
+ t.Errorf("N2YO_API_KEY = %q, want %q", val, "fake_test_key")
+ }
+ },
+ },
+ {
+ name: ".env file with comments",
+ envContent: `# This is a comment
+SPACE_TRACK_USERNAME=testuser
+# Another comment
+SPACE_TRACK_PASSWORD=testpass
+`,
+ expectError: false,
+ checkEnv: func(t *testing.T) {
+ if val := os.Getenv("SPACE_TRACK_USERNAME"); val != "testuser" {
+ t.Errorf("SPACE_TRACK_USERNAME = %q, want %q", val, "testuser")
+ }
+ if val := os.Getenv("SPACE_TRACK_PASSWORD"); val != "testpass" {
+ t.Errorf("SPACE_TRACK_PASSWORD = %q, want %q", val, "testpass")
+ }
+ },
+ },
+ {
+ name: ".env file with empty lines",
+ envContent: `SPACE_TRACK_USERNAME=testuser
+
+SPACE_TRACK_PASSWORD=testpass
+
+`,
+ expectError: false,
+ checkEnv: func(t *testing.T) {
+ if val := os.Getenv("SPACE_TRACK_USERNAME"); val != "testuser" {
+ t.Errorf("SPACE_TRACK_USERNAME = %q, want %q", val, "testuser")
+ }
+ if val := os.Getenv("SPACE_TRACK_PASSWORD"); val != "testpass" {
+ t.Errorf("SPACE_TRACK_PASSWORD = %q, want %q", val, "testpass")
+ }
+ },
+ },
+ {
+ name: ".env file with quoted values",
+ envContent: `SPACE_TRACK_USERNAME="testuser"
+SPACE_TRACK_PASSWORD='fake_test_password'
+N2YO_API_KEY="fake_test_key"
+`,
+ expectError: false,
+ checkEnv: func(t *testing.T) {
+ if val := os.Getenv("SPACE_TRACK_USERNAME"); val != "testuser" {
+ t.Errorf("SPACE_TRACK_USERNAME = %q, want %q", val, "testuser")
+ }
+ if val := os.Getenv("SPACE_TRACK_PASSWORD"); val != "fake_test_password" {
+ t.Errorf("SPACE_TRACK_PASSWORD = %q, want %q", val, "fake_test_password")
+ }
+ if val := os.Getenv("N2YO_API_KEY"); val != "fake_test_key" {
+ t.Errorf("N2YO_API_KEY = %q, want %q", val, "fake_test_key")
+ }
+ },
+ },
+ {
+ name: ".env file with spaces around equals",
+ envContent: `SPACE_TRACK_USERNAME = testuser
+SPACE_TRACK_PASSWORD = testpass
+`,
+ expectError: false,
+ checkEnv: func(t *testing.T) {
+ if val := os.Getenv("SPACE_TRACK_USERNAME"); val != "testuser" {
+ t.Errorf("SPACE_TRACK_USERNAME = %q, want %q", val, "testuser")
+ }
+ if val := os.Getenv("SPACE_TRACK_PASSWORD"); val != "testpass" {
+ t.Errorf("SPACE_TRACK_PASSWORD = %q, want %q", val, "testpass")
+ }
+ },
+ },
+ {
+ name: ".env file not found",
+ envContent: "",
+ expectError: true,
+ checkEnv: func(t *testing.T) {},
+ },
+ {
+ name: ".env file with invalid line (no equals)",
+ envContent: `SPACE_TRACK_USERNAME=testuser
+INVALID_LINE_WITHOUT_EQUALS
+SPACE_TRACK_PASSWORD=testpass
+`,
+ expectError: false,
+ checkEnv: func(t *testing.T) {
+ if val := os.Getenv("SPACE_TRACK_USERNAME"); val != "testuser" {
+ t.Errorf("SPACE_TRACK_USERNAME = %q, want %q", val, "testuser")
+ }
+ if val := os.Getenv("SPACE_TRACK_PASSWORD"); val != "testpass" {
+ t.Errorf("SPACE_TRACK_PASSWORD = %q, want %q", val, "testpass")
+ }
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Clean up environment variables before test
+ os.Unsetenv("SPACE_TRACK_USERNAME")
+ os.Unsetenv("SPACE_TRACK_PASSWORD")
+ os.Unsetenv("N2YO_API_KEY")
+
+ // Save original working directory
+ originalDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("Failed to get current directory: %v", err)
+ }
+
+ // Change to temporary directory
+ err = os.Chdir(tmpDir)
+ if err != nil {
+ t.Fatalf("Failed to change directory: %v", err)
+ }
+ defer os.Chdir(originalDir)
+
+ if tt.name == ".env file not found" {
+ // Ensure .env file doesn't exist in temp directory
+ envPath := filepath.Join(tmpDir, ".env")
+ os.Remove(envPath) // Remove if it exists
+ } else {
+ // Create .env file
+ envPath := filepath.Join(tmpDir, ".env")
+ err := os.WriteFile(envPath, []byte(tt.envContent), 0644)
+ if err != nil {
+ t.Fatalf("Failed to create .env file: %v", err)
+ }
+ }
+
+ // Test loadEnvFile
+ err = loadEnvFile()
+ if tt.expectError {
+ if err == nil {
+ t.Errorf("loadEnvFile() expected error, got nil")
+ }
+ if err != nil && !strings.Contains(err.Error(), ".env file not found") {
+ t.Errorf("loadEnvFile() error = %v, want error containing '.env file not found'", err)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("loadEnvFile() unexpected error: %v", err)
+ }
+ tt.checkEnv(t)
+ }
+ })
+ }
+}
+
+func TestCheckEnvironmentalVariable(t *testing.T) {
+ tests := []struct {
+ name string
+ envKey string
+ preSetValue string
+ shouldSet bool
+ }{
+ {
+ name: "Variable not set should trigger set",
+ envKey: "TEST_VAR_NOT_SET",
+ preSetValue: "",
+ shouldSet: true,
+ },
+ {
+ name: "Variable already set should not trigger set",
+ envKey: "TEST_VAR_SET",
+ preSetValue: "existing_value",
+ shouldSet: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Clean up before test
+ os.Unsetenv(tt.envKey)
+
+ // Pre-set value if needed
+ if tt.preSetValue != "" {
+ os.Setenv(tt.envKey, tt.preSetValue)
+ }
+
+ // Note: We can't easily test the interactive part (setEnvironmentalVariable)
+ // without mocking stdin, but we can test that checkEnvironmentalVariable
+ // correctly identifies when a variable is missing
+ _, found := os.LookupEnv(tt.envKey)
+ if found != (tt.preSetValue != "") {
+ t.Errorf("Environment variable lookup mismatch")
+ }
+
+ // Clean up after test
+ os.Unsetenv(tt.envKey)
+ })
+ }
+}
+
+func TestLoadEnvFileEdgeCases(t *testing.T) {
+ tmpDir := t.TempDir()
+ originalDir, _ := os.Getwd()
+ defer os.Chdir(originalDir)
+
+ t.Run("Empty .env file", func(t *testing.T) {
+ os.Chdir(tmpDir)
+ os.Unsetenv("TEST_VAR")
+
+ envPath := filepath.Join(tmpDir, ".env")
+ err := os.WriteFile(envPath, []byte(""), 0644)
+ if err != nil {
+ t.Fatalf("Failed to create .env file: %v", err)
+ }
+
+ err = loadEnvFile()
+ if err != nil {
+ t.Errorf("loadEnvFile() with empty file should not error, got: %v", err)
+ }
+ })
+
+ t.Run(".env file with only comments", func(t *testing.T) {
+ os.Chdir(tmpDir)
+ os.Unsetenv("TEST_VAR")
+
+ envPath := filepath.Join(tmpDir, ".env")
+ err := os.WriteFile(envPath, []byte("# Comment 1\n# Comment 2\n"), 0644)
+ if err != nil {
+ t.Fatalf("Failed to create .env file: %v", err)
+ }
+
+ err = loadEnvFile()
+ if err != nil {
+ t.Errorf("loadEnvFile() with only comments should not error, got: %v", err)
+ }
+ })
+
+ t.Run(".env file with value containing equals", func(t *testing.T) {
+ os.Chdir(tmpDir)
+ os.Unsetenv("TEST_VAR")
+
+ envPath := filepath.Join(tmpDir, ".env")
+ // SplitN with limit 2 should handle this correctly
+ err := os.WriteFile(envPath, []byte("TEST_VAR=value=with=equals\n"), 0644)
+ if err != nil {
+ t.Fatalf("Failed to create .env file: %v", err)
+ }
+
+ err = loadEnvFile()
+ if err != nil {
+ t.Errorf("loadEnvFile() should not error, got: %v", err)
+ }
+
+ val := os.Getenv("TEST_VAR")
+ if val != "value=with=equals" {
+ t.Errorf("TEST_VAR = %q, want %q", val, "value=with=equals")
+ }
+ })
+
+ t.Run(".env file with whitespace-only lines", func(t *testing.T) {
+ os.Chdir(tmpDir)
+ os.Unsetenv("TEST_VAR")
+
+ envPath := filepath.Join(tmpDir, ".env")
+ err := os.WriteFile(envPath, []byte(" \n\t\nTEST_VAR=value\n \n"), 0644)
+ if err != nil {
+ t.Fatalf("Failed to create .env file: %v", err)
+ }
+
+ err = loadEnvFile()
+ if err != nil {
+ t.Errorf("loadEnvFile() should not error, got: %v", err)
+ }
+
+ val := os.Getenv("TEST_VAR")
+ if val != "value" {
+ t.Errorf("TEST_VAR = %q, want %q", val, "value")
+ }
+ })
+}
+
+// Benchmark tests
+func BenchmarkIsPasswordField(b *testing.B) {
+ testCases := []string{
+ "SPACE_TRACK_PASSWORD",
+ "N2YO_API_KEY",
+ "SPACE_TRACK_USERNAME",
+ "MY_PASSWORD",
+ "REGULAR_VAR",
+ }
+
+ for i := 0; i < b.N; i++ {
+ for _, tc := range testCases {
+ isPasswordField(tc)
+ }
+ }
+}
+
+func BenchmarkLoadEnvFile(b *testing.B) {
+ tmpDir := b.TempDir()
+ originalDir, _ := os.Getwd()
+ defer os.Chdir(originalDir)
+
+ envContent := `SPACE_TRACK_USERNAME=testuser
+SPACE_TRACK_PASSWORD=fake_test_password
+N2YO_API_KEY=fake_test_key
+`
+
+ envPath := filepath.Join(tmpDir, ".env")
+ err := os.WriteFile(envPath, []byte(envContent), 0644)
+ if err != nil {
+ b.Fatalf("Failed to create .env file: %v", err)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ os.Chdir(tmpDir)
+ os.Unsetenv("SPACE_TRACK_USERNAME")
+ os.Unsetenv("SPACE_TRACK_PASSWORD")
+ os.Unsetenv("N2YO_API_KEY")
+ loadEnvFile()
+ }
+}
diff --git a/osint/batch.go b/osint/batch.go
new file mode 100644
index 0000000..b203991
--- /dev/null
+++ b/osint/batch.go
@@ -0,0 +1,1039 @@
+package osint
+
+import (
+ "encoding/csv"
+ "encoding/json"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/TwiN/go-color"
+ "github.com/manifoldco/promptui"
+)
+
+// BatchSatellite represents a satellite selected for batch processing.
+type BatchSatellite struct {
+ Name string
+ NORADID string
+ Country string
+ ObjectType string
+}
+
+// BatchTLEResult contains TLE data for a satellite in batch processing.
+type BatchTLEResult struct {
+ Satellite BatchSatellite
+ TLE TLE
+ Error error
+ Success bool
+}
+
+// BatchComparisonResult contains comparison data for multiple satellites.
+type BatchComparisonResult struct {
+ Satellites []BatchSatellite
+ Results []BatchTLEResult
+ Summary BatchSummary
+}
+
+// BatchSummary provides aggregate statistics for batch operations.
+type BatchSummary struct {
+ TotalProcessed int
+ Successful int
+ Failed int
+ AverageInclination float64
+ AverageMeanMotion float64
+ LowestAltitude float64
+ HighestAltitude float64
+}
+
+// showBatchMenu displays the batch operations menu.
+func showBatchMenu() string {
+ menuItems := []string{
+ "Download TLE for Multiple Satellites",
+ "Compare Multiple Satellites",
+ "Batch Visual Predictions",
+ "Batch Radio Predictions",
+ "Batch Position Data",
+ "Cancel",
+ }
+
+ prompt := promptui.Select{
+ Label: "Batch Operations Menu",
+ Items: menuItems,
+ Size: 10,
+ }
+
+ idx, _, err := prompt.Run()
+ if err != nil || idx == 5 {
+ return ""
+ }
+
+ options := []string{"tle", "compare", "visual", "radio", "position"}
+ if idx < len(options) {
+ return options[idx]
+ }
+ return ""
+}
+
+// selectMultipleSatellites allows users to select multiple satellites for batch processing.
+func selectMultipleSatellites() []BatchSatellite {
+ var selected []BatchSatellite
+ selectedMap := make(map[string]bool) // Track selected NORAD IDs to prevent duplicates
+
+ for {
+ fmt.Println(color.Ize(color.Cyan, "\n [*] Current selection: "+strconv.Itoa(len(selected))+" satellite(s)"))
+
+ menuItems := []string{
+ "Add Satellite from Catalog",
+ "Add Satellite by NORAD ID",
+ "Add from Favorites",
+ "Remove Satellite",
+ "View Selected Satellites",
+ "Clear All",
+ "Done - Process Batch",
+ "Cancel",
+ }
+
+ prompt := promptui.Select{
+ Label: "Batch Selection Menu",
+ Items: menuItems,
+ Size: 10,
+ }
+
+ idx, _, err := prompt.Run()
+ if err != nil || idx == 7 {
+ return nil
+ }
+
+ switch idx {
+ case 0: // Add from Catalog
+ result := SelectSatellite()
+ if result != "" {
+ norad := extractNorad(result)
+ if !selectedMap[norad] {
+ // Fetch full satellite info
+ client, err := Login()
+ if err == nil {
+ endpoint := fmt.Sprintf("/class/satcat/NORAD_CAT_ID/%s/format/json", norad)
+ data, err := QuerySpaceTrack(client, endpoint)
+ if err == nil {
+ var sats []Satellite
+ if json.Unmarshal([]byte(data), &sats) == nil && len(sats) > 0 {
+ sat := sats[0]
+ selected = append(selected, BatchSatellite{
+ Name: sat.SATNAME,
+ NORADID: sat.NORAD_CAT_ID,
+ Country: sat.COUNTRY,
+ ObjectType: sat.OBJECT_TYPE,
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: "+sat.SATNAME))
+ } else {
+ // Fallback with just name and NORAD
+ name := strings.Split(result, " (")[0]
+ selected = append(selected, BatchSatellite{
+ Name: name,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: "+name))
+ }
+ } else {
+ // Fallback
+ name := strings.Split(result, " (")[0]
+ selected = append(selected, BatchSatellite{
+ Name: name,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: "+name))
+ }
+ } else {
+ // Fallback
+ name := strings.Split(result, " (")[0]
+ selected = append(selected, BatchSatellite{
+ Name: name,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: "+name))
+ }
+ } else {
+ fmt.Println(color.Ize(color.Yellow, " [!] Satellite already in batch"))
+ }
+ }
+
+ case 1: // Add by NORAD ID
+ noradPrompt := promptui.Prompt{
+ Label: "Enter NORAD ID",
+ Validate: func(input string) error {
+ if strings.TrimSpace(input) == "" {
+ return fmt.Errorf("NORAD ID cannot be empty")
+ }
+ return nil
+ },
+ }
+ norad, err := noradPrompt.Run()
+ if err == nil && norad != "" {
+ norad = strings.TrimSpace(norad)
+ if !selectedMap[norad] {
+ // Try to fetch satellite name
+ client, err := Login()
+ if err == nil {
+ endpoint := fmt.Sprintf("/class/satcat/NORAD_CAT_ID/%s/format/json", norad)
+ data, err := QuerySpaceTrack(client, endpoint)
+ if err == nil {
+ var sats []Satellite
+ if json.Unmarshal([]byte(data), &sats) == nil && len(sats) > 0 {
+ sat := sats[0]
+ selected = append(selected, BatchSatellite{
+ Name: sat.SATNAME,
+ NORADID: sat.NORAD_CAT_ID,
+ Country: sat.COUNTRY,
+ ObjectType: sat.OBJECT_TYPE,
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: "+sat.SATNAME))
+ } else {
+ selected = append(selected, BatchSatellite{
+ Name: "NORAD " + norad,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: NORAD "+norad))
+ }
+ } else {
+ selected = append(selected, BatchSatellite{
+ Name: "NORAD " + norad,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: NORAD "+norad))
+ }
+ } else {
+ selected = append(selected, BatchSatellite{
+ Name: "NORAD " + norad,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: NORAD "+norad))
+ }
+ } else {
+ fmt.Println(color.Ize(color.Yellow, " [!] Satellite already in batch"))
+ }
+ }
+
+ case 2: // Add from Favorites
+ favResult := SelectFromFavorites()
+ if favResult != "" {
+ norad := extractNorad(favResult)
+ if !selectedMap[norad] {
+ name := strings.Split(favResult, " (")[0]
+ selected = append(selected, BatchSatellite{
+ Name: name,
+ NORADID: norad,
+ Country: "Unknown",
+ ObjectType: "Unknown",
+ })
+ selectedMap[norad] = true
+ fmt.Println(color.Ize(color.Green, " [+] Added: "+name))
+ } else {
+ fmt.Println(color.Ize(color.Yellow, " [!] Satellite already in batch"))
+ }
+ }
+
+ case 3: // Remove Satellite
+ if len(selected) == 0 {
+ fmt.Println(color.Ize(color.Yellow, " [!] No satellites to remove"))
+ continue
+ }
+ var items []string
+ for i, sat := range selected {
+ items = append(items, fmt.Sprintf("%d. %s (%s)", i+1, sat.Name, sat.NORADID))
+ }
+ items = append(items, "Cancel")
+
+ removePrompt := promptui.Select{
+ Label: "Select satellite to remove",
+ Items: items,
+ }
+ removeIdx, _, err := removePrompt.Run()
+ if err == nil && removeIdx < len(selected) {
+ removed := selected[removeIdx]
+ selected = append(selected[:removeIdx], selected[removeIdx+1:]...)
+ delete(selectedMap, removed.NORADID)
+ fmt.Println(color.Ize(color.Green, " [+] Removed: "+removed.Name))
+ }
+
+ case 4: // View Selected
+ if len(selected) == 0 {
+ fmt.Println(color.Ize(color.Yellow, " [!] No satellites selected"))
+ } else {
+ fmt.Println(color.Ize(color.Cyan, "\n Selected Satellites:"))
+ for i, sat := range selected {
+ fmt.Printf(" %d. %s (%s)\n", i+1, sat.Name, sat.NORADID)
+ if sat.Country != "Unknown" {
+ fmt.Printf(" Country: %s\n", sat.Country)
+ }
+ if sat.ObjectType != "Unknown" {
+ fmt.Printf(" Type: %s\n", sat.ObjectType)
+ }
+ }
+ }
+
+ case 5: // Clear All
+ if len(selected) > 0 {
+ confirmPrompt := promptui.Prompt{
+ Label: "Clear all satellites? (y/n)",
+ Default: "n",
+ AllowEdit: true,
+ }
+ confirm, _ := confirmPrompt.Run()
+ if strings.ToLower(strings.TrimSpace(confirm)) == "y" {
+ selected = []BatchSatellite{}
+ selectedMap = make(map[string]bool)
+ fmt.Println(color.Ize(color.Green, " [+] Cleared all satellites"))
+ }
+ }
+
+ case 6: // Done
+ if len(selected) == 0 {
+ fmt.Println(color.Ize(color.Red, " [!] Please select at least one satellite"))
+ continue
+ }
+ return selected
+ }
+ }
+}
+
+// BatchDownloadTLE downloads TLE data for multiple satellites concurrently.
+func BatchDownloadTLE(satellites []BatchSatellite) []BatchTLEResult {
+ if len(satellites) == 0 {
+ return nil
+ }
+
+ fmt.Println(color.Ize(color.Cyan, fmt.Sprintf("\n [*] Downloading TLE data for %d satellite(s)...", len(satellites))))
+
+ client, err := Login()
+ if err != nil {
+ fmt.Println(color.Ize(color.Red, " [!] ERROR: Failed to login: "+err.Error()))
+ return nil
+ }
+
+ results := make([]BatchTLEResult, len(satellites))
+ var wg sync.WaitGroup
+ var mu sync.Mutex
+ completed := 0
+
+ for i, sat := range satellites {
+ wg.Add(1)
+ go func(idx int, satellite BatchSatellite) {
+ defer wg.Done()
+
+ result := BatchTLEResult{
+ Satellite: satellite,
+ Success: false,
+ }
+
+ endpoint := fmt.Sprintf("/class/gp_history/format/tle/NORAD_CAT_ID/%s/orderby/EPOCH%%20desc/limit/1", satellite.NORADID)
+ data, err := QuerySpaceTrack(client, endpoint)
+ if err != nil {
+ result.Error = err
+ mu.Lock()
+ results[idx] = result
+ completed++
+ mu.Unlock()
+ return
+ }
+
+ lines := strings.Split(strings.TrimSpace(data), "\n")
+ var lineOne, lineTwo string
+
+ if len(lines) >= 2 {
+ lineOne = strings.TrimSpace(lines[0])
+ lineTwo = strings.TrimSpace(lines[1])
+ } else {
+ tleLines := strings.Fields(data)
+ if len(tleLines) >= 2 {
+ mid := len(tleLines) / 2
+ if mid < 1 {
+ mid = 1
+ }
+ if mid >= len(tleLines) {
+ mid = len(tleLines) - 1
+ }
+ lineOne = strings.Join(tleLines[:mid], " ")
+ lineTwo = strings.Join(tleLines[mid:], " ")
+ } else {
+ result.Error = fmt.Errorf("insufficient TLE data")
+ mu.Lock()
+ results[idx] = result
+ completed++
+ mu.Unlock()
+ return
+ }
+ }
+
+ if !strings.HasPrefix(lineOne, "1 ") || !strings.HasPrefix(lineTwo, "2 ") {
+ result.Error = fmt.Errorf("invalid TLE format")
+ mu.Lock()
+ results[idx] = result
+ completed++
+ mu.Unlock()
+ return
+ }
+
+ tle := ConstructTLE(satellite.Name, lineOne, lineTwo)
+
+ // Validate parsing
+ line1Fields := strings.Fields(lineOne)
+ line2Fields := strings.Fields(lineTwo)
+ if len(line1Fields) < 4 || len(line2Fields) < 3 {
+ result.Error = fmt.Errorf("insufficient fields in TLE")
+ mu.Lock()
+ results[idx] = result
+ completed++
+ mu.Unlock()
+ return
+ }
+
+ if tle.SatelliteCatalogNumber == 0 && tle.InternationalDesignator == "" && tle.ElementSetEpoch == 0.0 {
+ result.Error = fmt.Errorf("failed to parse TLE data")
+ mu.Lock()
+ results[idx] = result
+ completed++
+ mu.Unlock()
+ return
+ }
+
+ result.TLE = tle
+ result.Success = true
+
+ mu.Lock()
+ results[idx] = result
+ completed++
+ mu.Unlock()
+
+ fmt.Printf(color.Ize(color.Green, " [+] [%d/%d] Downloaded: %s\n"), completed, len(satellites), satellite.Name)
+ }(i, sat)
+ }
+
+ wg.Wait()
+
+ // Display summary
+ successful := 0
+ for _, r := range results {
+ if r.Success {
+ successful++
+ }
+ }
+
+ fmt.Println(color.Ize(color.Cyan, fmt.Sprintf("\n [*] Batch download complete: %d/%d successful", successful, len(satellites))))
+
+ return results
+}
+
+// CompareSatellites compares TLE data for multiple satellites and displays a summary.
+func CompareSatellites(results []BatchTLEResult) BatchComparisonResult {
+ if len(results) == 0 {
+ return BatchComparisonResult{}
+ }
+
+ comparison := BatchComparisonResult{
+ Results: results,
+ Summary: BatchSummary{
+ TotalProcessed: len(results),
+ },
+ }
+
+ var inclinations []float64
+ var meanMotions []float64
+ var altitudes []float64
+
+ for _, result := range results {
+ if result.Success {
+ comparison.Summary.Successful++
+ comparison.Satellites = append(comparison.Satellites, result.Satellite)
+
+ if result.TLE.OrbitInclination > 0 {
+ inclinations = append(inclinations, result.TLE.OrbitInclination)
+ }
+ if result.TLE.MeanMotion > 0 {
+ meanMotions = append(meanMotions, result.TLE.MeanMotion)
+ }
+ // Estimate altitude from mean motion (rough calculation)
+ if result.TLE.MeanMotion > 0 {
+ // Simplified: altitude ≈ (GM / (4π² * meanMotion²))^(1/3) - Earth radius
+ // For simplicity, using approximate formula
+ period := 86400.0 / result.TLE.MeanMotion // period in seconds
+ // Rough altitude estimate (km)
+ altitude := (398600.4418 * period * period / (4 * 3.14159 * 3.14159)) / 1000 - 6371
+ if altitude > 0 {
+ altitudes = append(altitudes, altitude)
+ }
+ }
+ } else {
+ comparison.Summary.Failed++
+ }
+ }
+
+ // Calculate averages
+ if len(inclinations) > 0 {
+ sum := 0.0
+ for _, inc := range inclinations {
+ sum += inc
+ }
+ comparison.Summary.AverageInclination = sum / float64(len(inclinations))
+ }
+
+ if len(meanMotions) > 0 {
+ sum := 0.0
+ for _, mm := range meanMotions {
+ sum += mm
+ }
+ comparison.Summary.AverageMeanMotion = sum / float64(len(meanMotions))
+ }
+
+ if len(altitudes) > 0 {
+ min := altitudes[0]
+ max := altitudes[0]
+ for _, alt := range altitudes {
+ if alt < min {
+ min = alt
+ }
+ if alt > max {
+ max = alt
+ }
+ }
+ comparison.Summary.LowestAltitude = min
+ comparison.Summary.HighestAltitude = max
+ }
+
+ return comparison
+}
+
+// DisplayComparison displays the comparison results in a formatted table.
+func DisplayComparison(comparison BatchComparisonResult) {
+ if len(comparison.Results) == 0 {
+ fmt.Println(color.Ize(color.Yellow, " [!] No data to compare"))
+ return
+ }
+
+ fmt.Println(color.Ize(color.Purple, "\n╔═════════════════════════════════════════════════════════════╗"))
+ fmt.Println(color.Ize(color.Purple, "║ Satellite Comparison Summary ║"))
+ fmt.Println(color.Ize(color.Purple, "╠═════════════════════════════════════════════════════════════╣"))
+
+ fmt.Println(color.Ize(color.Purple, GenRowString("Total Processed", strconv.Itoa(comparison.Summary.TotalProcessed))))
+ fmt.Println(color.Ize(color.Purple, GenRowString("Successful", strconv.Itoa(comparison.Summary.Successful))))
+ fmt.Println(color.Ize(color.Purple, GenRowString("Failed", strconv.Itoa(comparison.Summary.Failed))))
+
+ if comparison.Summary.AverageInclination > 0 {
+ fmt.Println(color.Ize(color.Purple, GenRowString("Average Inclination", fmt.Sprintf("%.2f°", comparison.Summary.AverageInclination))))
+ }
+ if comparison.Summary.AverageMeanMotion > 0 {
+ fmt.Println(color.Ize(color.Purple, GenRowString("Average Mean Motion", fmt.Sprintf("%.4f rev/day", comparison.Summary.AverageMeanMotion))))
+ }
+ if comparison.Summary.LowestAltitude > 0 {
+ fmt.Println(color.Ize(color.Purple, GenRowString("Lowest Altitude (est.)", fmt.Sprintf("%.2f km", comparison.Summary.LowestAltitude))))
+ }
+ if comparison.Summary.HighestAltitude > 0 {
+ fmt.Println(color.Ize(color.Purple, GenRowString("Highest Altitude (est.)", fmt.Sprintf("%.2f km", comparison.Summary.HighestAltitude))))
+ }
+
+ fmt.Println(color.Ize(color.Purple, "╠═════════════════════════════════════════════════════════════╣"))
+ fmt.Println(color.Ize(color.Purple, "║ Individual Results ║"))
+ fmt.Println(color.Ize(color.Purple, "╠═════════════════════════════════════════════════════════════╣"))
+
+ for i, result := range comparison.Results {
+ status := "✅ Success"
+ if !result.Success {
+ status = "❌ Failed"
+ if result.Error != nil {
+ status += ": " + result.Error.Error()
+ }
+ }
+
+ fmt.Println(color.Ize(color.Purple, GenRowString(fmt.Sprintf("Satellite %d", i+1), result.Satellite.Name)))
+ fmt.Println(color.Ize(color.Purple, GenRowString(" NORAD ID", result.Satellite.NORADID)))
+ fmt.Println(color.Ize(color.Purple, GenRowString(" Status", status)))
+
+ if result.Success {
+ fmt.Println(color.Ize(color.Purple, GenRowString(" Inclination", fmt.Sprintf("%.2f°", result.TLE.OrbitInclination))))
+ fmt.Println(color.Ize(color.Purple, GenRowString(" Mean Motion", fmt.Sprintf("%.4f rev/day", result.TLE.MeanMotion))))
+ fmt.Println(color.Ize(color.Purple, GenRowString(" Eccentricity", fmt.Sprintf("%.6f", result.TLE.Eccentrcity))))
+ }
+
+ if i < len(comparison.Results)-1 {
+ fmt.Println(color.Ize(color.Purple, "╠═════════════════════════════════════════════════════════════╣"))
+ }
+ }
+
+ fmt.Println(color.Ize(color.Purple, "╚═════════════════════════════════════════════════════════════╝\n\n"))
+}
+
+// BatchOperations provides the main entry point for batch operations.
+func BatchOperations() {
+ operation := showBatchMenu()
+ if operation == "" {
+ return
+ }
+
+ satellites := selectMultipleSatellites()
+ if len(satellites) == 0 {
+ return
+ }
+
+ switch operation {
+ case "tle":
+ results := BatchDownloadTLE(satellites)
+ if len(results) > 0 {
+ // Display results
+ fmt.Println(color.Ize(color.Cyan, "\n [*] Batch TLE Download Results:"))
+ for i, result := range results {
+ if result.Success {
+ fmt.Printf("\n %d. %s (%s) - ✅ Success\n", i+1, result.Satellite.Name, result.Satellite.NORADID)
+ PrintTLE(result.TLE)
+ } else {
+ fmt.Printf("\n %d. %s (%s) - ❌ Failed", i+1, result.Satellite.Name, result.Satellite.NORADID)
+ if result.Error != nil {
+ fmt.Printf(": %s\n", result.Error.Error())
+ } else {
+ fmt.Println()
+ }
+ }
+ }
+
+ // Offer export
+ exportPrompt := promptui.Prompt{
+ Label: "Export batch results? (y/n)",
+ Default: "n",
+ AllowEdit: true,
+ }
+ exportAnswer, _ := exportPrompt.Run()
+ if strings.ToLower(strings.TrimSpace(exportAnswer)) == "y" {
+ exportBatchTLE(results)
+ }
+ }
+
+ case "compare":
+ results := BatchDownloadTLE(satellites)
+ if len(results) > 0 {
+ comparison := CompareSatellites(results)
+ DisplayComparison(comparison)
+
+ // Offer export
+ exportPrompt := promptui.Prompt{
+ Label: "Export comparison results? (y/n)",
+ Default: "n",
+ AllowEdit: true,
+ }
+ exportAnswer, _ := exportPrompt.Run()
+ if strings.ToLower(strings.TrimSpace(exportAnswer)) == "y" {
+ exportBatchComparison(comparison)
+ }
+ }
+
+ case "visual", "radio", "position":
+ fmt.Println(color.Ize(color.Yellow, " [!] Batch predictions and positions coming soon"))
+ // TODO: Implement batch predictions and positions
+ }
+}
+
+// exportBatchTLE exports batch TLE results to a file.
+func exportBatchTLE(results []BatchTLEResult) {
+ formatPrompt := promptui.Select{
+ Label: "Select Export Format",
+ Items: []string{"CSV", "JSON", "Text", "Cancel"},
+ }
+ formatIdx, formatChoice, err := formatPrompt.Run()
+ if err != nil || formatIdx == 3 {
+ return
+ }
+
+ pathPrompt := promptui.Prompt{
+ Label: "Enter file path",
+ Default: fmt.Sprintf("batch_tle_%s", time.Now().Format("20060102_150405")),
+ AllowEdit: true,
+ }
+ filePath, err := pathPrompt.Run()
+ if err != nil {
+ return
+ }
+
+ filePath = strings.TrimSpace(filePath)
+ if filePath == "" {
+ filePath = fmt.Sprintf("batch_tle_%s", time.Now().Format("20060102_150405"))
+ }
+
+ ext := ""
+ switch formatChoice {
+ case "CSV":
+ ext = ".csv"
+ case "JSON":
+ ext = ".json"
+ case "Text":
+ ext = ".txt"
+ }
+
+ if !strings.HasSuffix(filePath, ext) {
+ filePath += ext
+ }
+
+ switch formatChoice {
+ case "CSV":
+ exportBatchTLECSV(results, filePath)
+ case "JSON":
+ exportBatchTLEJSON(results, filePath)
+ case "Text":
+ exportBatchTLEText(results, filePath)
+ }
+}
+
+// exportBatchComparison exports comparison results to a file.
+func exportBatchComparison(comparison BatchComparisonResult) {
+ formatPrompt := promptui.Select{
+ Label: "Select Export Format",
+ Items: []string{"CSV", "JSON", "Text", "Cancel"},
+ }
+ formatIdx, formatChoice, err := formatPrompt.Run()
+ if err != nil || formatIdx == 3 {
+ return
+ }
+
+ pathPrompt := promptui.Prompt{
+ Label: "Enter file path",
+ Default: fmt.Sprintf("batch_comparison_%s", time.Now().Format("20060102_150405")),
+ AllowEdit: true,
+ }
+ filePath, err := pathPrompt.Run()
+ if err != nil {
+ return
+ }
+
+ filePath = strings.TrimSpace(filePath)
+ if filePath == "" {
+ filePath = fmt.Sprintf("batch_comparison_%s", time.Now().Format("20060102_150405"))
+ }
+
+ ext := ""
+ switch formatChoice {
+ case "CSV":
+ ext = ".csv"
+ case "JSON":
+ ext = ".json"
+ case "Text":
+ ext = ".txt"
+ }
+
+ if !strings.HasSuffix(filePath, ext) {
+ filePath += ext
+ }
+
+ switch formatChoice {
+ case "CSV":
+ exportBatchComparisonCSV(comparison, filePath)
+ case "JSON":
+ exportBatchComparisonJSON(comparison, filePath)
+ case "Text":
+ exportBatchComparisonText(comparison, filePath)
+ }
+}
+
+// exportBatchTLECSV exports batch TLE results to CSV format.
+func exportBatchTLECSV(results []BatchTLEResult, filePath string) error {
+ file, err := os.Create(filePath)
+ if err != nil {
+ return fmt.Errorf("failed to create file: %w", err)
+ }
+ defer file.Close()
+
+ writer := csv.NewWriter(file)
+ defer writer.Flush()
+
+ // Write header
+ headers := []string{
+ "Satellite Name", "NORAD ID", "Status", "Common Name", "Catalog Number",
+ "Inclination", "Mean Motion", "Eccentricity", "Error",
+ }
+ if err := writer.Write(headers); err != nil {
+ return fmt.Errorf("failed to write header: %w", err)
+ }
+
+ // Write data
+ for _, result := range results {
+ status := "Success"
+ errorMsg := ""
+ if !result.Success {
+ status = "Failed"
+ if result.Error != nil {
+ errorMsg = result.Error.Error()
+ }
+ }
+
+ row := []string{
+ result.Satellite.Name,
+ result.Satellite.NORADID,
+ status,
+ }
+
+ if result.Success {
+ row = append(row,
+ result.TLE.CommonName,
+ strconv.Itoa(result.TLE.SatelliteCatalogNumber),
+ fmt.Sprintf("%.2f", result.TLE.OrbitInclination),
+ fmt.Sprintf("%.4f", result.TLE.MeanMotion),
+ fmt.Sprintf("%.6f", result.TLE.Eccentrcity),
+ errorMsg,
+ )
+ } else {
+ row = append(row, "", "", "", "", "", errorMsg)
+ }
+
+ if err := writer.Write(row); err != nil {
+ return fmt.Errorf("failed to write row: %w", err)
+ }
+ }
+
+ fmt.Println(color.Ize(color.Green, " [+] Exported to: "+filePath))
+ return nil
+}
+
+// exportBatchTLEJSON exports batch TLE results to JSON format.
+func exportBatchTLEJSON(results []BatchTLEResult, filePath string) error {
+ type ExportResult struct {
+ Satellite BatchSatellite `json:"satellite"`
+ TLE *TLE `json:"tle,omitempty"`
+ Success bool `json:"success"`
+ Error string `json:"error,omitempty"`
+ }
+
+ var exportResults []ExportResult
+ for _, result := range results {
+ exportResult := ExportResult{
+ Satellite: result.Satellite,
+ Success: result.Success,
+ }
+ if result.Success {
+ exportResult.TLE = &result.TLE
+ }
+ if result.Error != nil {
+ exportResult.Error = result.Error.Error()
+ }
+ exportResults = append(exportResults, exportResult)
+ }
+
+ data := map[string]interface{}{
+ "batch_results": exportResults,
+ "export_timestamp": time.Now().Format(time.RFC3339),
+ "total_count": len(results),
+ }
+
+ jsonData, err := json.MarshalIndent(data, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal JSON: %w", err)
+ }
+
+ if err := os.WriteFile(filePath, jsonData, 0644); err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
+
+ fmt.Println(color.Ize(color.Green, " [+] Exported to: "+filePath))
+ return nil
+}
+
+// exportBatchTLEText exports batch TLE results to text format.
+func exportBatchTLEText(results []BatchTLEResult, filePath string) error {
+ var builder strings.Builder
+
+ builder.WriteString("Batch TLE Download Results\n")
+ builder.WriteString(strings.Repeat("=", 60) + "\n\n")
+ builder.WriteString(fmt.Sprintf("Total Satellites: %d\n", len(results)))
+ builder.WriteString(fmt.Sprintf("Export Date: %s\n\n", time.Now().Format(time.RFC3339)))
+
+ for i, result := range results {
+ builder.WriteString(fmt.Sprintf("Satellite %d: %s (%s)\n", i+1, result.Satellite.Name, result.Satellite.NORADID))
+ if result.Success {
+ builder.WriteString("Status: ✅ Success\n")
+ builder.WriteString(fmt.Sprintf(" Common Name: %s\n", result.TLE.CommonName))
+ builder.WriteString(fmt.Sprintf(" Catalog Number: %d\n", result.TLE.SatelliteCatalogNumber))
+ builder.WriteString(fmt.Sprintf(" Inclination: %.2f°\n", result.TLE.OrbitInclination))
+ builder.WriteString(fmt.Sprintf(" Mean Motion: %.4f rev/day\n", result.TLE.MeanMotion))
+ builder.WriteString(fmt.Sprintf(" Eccentricity: %.6f\n", result.TLE.Eccentrcity))
+ } else {
+ builder.WriteString("Status: ❌ Failed\n")
+ if result.Error != nil {
+ builder.WriteString(fmt.Sprintf(" Error: %s\n", result.Error.Error()))
+ }
+ }
+ builder.WriteString("\n")
+ }
+
+ if err := os.WriteFile(filePath, []byte(builder.String()), 0644); err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
+
+ fmt.Println(color.Ize(color.Green, " [+] Exported to: "+filePath))
+ return nil
+}
+
+// exportBatchComparisonCSV exports comparison results to CSV format.
+func exportBatchComparisonCSV(comparison BatchComparisonResult, filePath string) error {
+ file, err := os.Create(filePath)
+ if err != nil {
+ return fmt.Errorf("failed to create file: %w", err)
+ }
+ defer file.Close()
+
+ writer := csv.NewWriter(file)
+ defer writer.Flush()
+
+ // Write summary
+ summaryHeaders := []string{"Metric", "Value"}
+ if err := writer.Write(summaryHeaders); err != nil {
+ return fmt.Errorf("failed to write header: %w", err)
+ }
+
+ summaryRows := [][]string{
+ {"Total Processed", strconv.Itoa(comparison.Summary.TotalProcessed)},
+ {"Successful", strconv.Itoa(comparison.Summary.Successful)},
+ {"Failed", strconv.Itoa(comparison.Summary.Failed)},
+ }
+ if comparison.Summary.AverageInclination > 0 {
+ summaryRows = append(summaryRows, []string{"Average Inclination", fmt.Sprintf("%.2f", comparison.Summary.AverageInclination)})
+ }
+ if comparison.Summary.AverageMeanMotion > 0 {
+ summaryRows = append(summaryRows, []string{"Average Mean Motion", fmt.Sprintf("%.4f", comparison.Summary.AverageMeanMotion)})
+ }
+
+ for _, row := range summaryRows {
+ if err := writer.Write(row); err != nil {
+ return fmt.Errorf("failed to write summary row: %w", err)
+ }
+ }
+
+ // Empty row separator
+ writer.Write([]string{})
+
+ // Write individual results
+ resultHeaders := []string{"Name", "NORAD ID", "Status", "Inclination", "Mean Motion", "Eccentricity", "Error"}
+ if err := writer.Write(resultHeaders); err != nil {
+ return fmt.Errorf("failed to write result header: %w", err)
+ }
+
+ for _, result := range comparison.Results {
+ status := "Success"
+ errorMsg := ""
+ if !result.Success {
+ status = "Failed"
+ if result.Error != nil {
+ errorMsg = result.Error.Error()
+ }
+ }
+
+ row := []string{
+ result.Satellite.Name,
+ result.Satellite.NORADID,
+ status,
+ }
+
+ if result.Success {
+ row = append(row,
+ fmt.Sprintf("%.2f", result.TLE.OrbitInclination),
+ fmt.Sprintf("%.4f", result.TLE.MeanMotion),
+ fmt.Sprintf("%.6f", result.TLE.Eccentrcity),
+ errorMsg,
+ )
+ } else {
+ row = append(row, "", "", errorMsg)
+ }
+
+ if err := writer.Write(row); err != nil {
+ return fmt.Errorf("failed to write result row: %w", err)
+ }
+ }
+
+ fmt.Println(color.Ize(color.Green, " [+] Exported to: "+filePath))
+ return nil
+}
+
+// exportBatchComparisonJSON exports comparison results to JSON format.
+func exportBatchComparisonJSON(comparison BatchComparisonResult, filePath string) error {
+ data := map[string]interface{}{
+ "summary": comparison.Summary,
+ "results": comparison.Results,
+ "export_timestamp": time.Now().Format(time.RFC3339),
+ }
+
+ jsonData, err := json.MarshalIndent(data, "", " ")
+ if err != nil {
+ return fmt.Errorf("failed to marshal JSON: %w", err)
+ }
+
+ if err := os.WriteFile(filePath, jsonData, 0644); err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
+
+ fmt.Println(color.Ize(color.Green, " [+] Exported to: "+filePath))
+ return nil
+}
+
+// exportBatchComparisonText exports comparison results to text format.
+func exportBatchComparisonText(comparison BatchComparisonResult, filePath string) error {
+ var builder strings.Builder
+
+ builder.WriteString("Satellite Comparison Results\n")
+ builder.WriteString(strings.Repeat("=", 60) + "\n\n")
+
+ builder.WriteString("Summary:\n")
+ builder.WriteString(fmt.Sprintf(" Total Processed: %d\n", comparison.Summary.TotalProcessed))
+ builder.WriteString(fmt.Sprintf(" Successful: %d\n", comparison.Summary.Successful))
+ builder.WriteString(fmt.Sprintf(" Failed: %d\n", comparison.Summary.Failed))
+ if comparison.Summary.AverageInclination > 0 {
+ builder.WriteString(fmt.Sprintf(" Average Inclination: %.2f°\n", comparison.Summary.AverageInclination))
+ }
+ if comparison.Summary.AverageMeanMotion > 0 {
+ builder.WriteString(fmt.Sprintf(" Average Mean Motion: %.4f rev/day\n", comparison.Summary.AverageMeanMotion))
+ }
+
+ builder.WriteString("\nIndividual Results:\n")
+ builder.WriteString(strings.Repeat("-", 60) + "\n")
+
+ for i, result := range comparison.Results {
+ builder.WriteString(fmt.Sprintf("\n%d. %s (%s)\n", i+1, result.Satellite.Name, result.Satellite.NORADID))
+ if result.Success {
+ builder.WriteString(" Status: ✅ Success\n")
+ builder.WriteString(fmt.Sprintf(" Inclination: %.2f°\n", result.TLE.OrbitInclination))
+ builder.WriteString(fmt.Sprintf(" Mean Motion: %.4f rev/day\n", result.TLE.MeanMotion))
+ builder.WriteString(fmt.Sprintf(" Eccentricity: %.6f\n", result.TLE.Eccentrcity))
+ } else {
+ builder.WriteString(" Status: ❌ Failed\n")
+ if result.Error != nil {
+ builder.WriteString(fmt.Sprintf(" Error: %s\n", result.Error.Error()))
+ }
+ }
+ }
+
+ builder.WriteString(fmt.Sprintf("\nExported: %s\n", time.Now().Format(time.RFC3339)))
+
+ if err := os.WriteFile(filePath, []byte(builder.String()), 0644); err != nil {
+ return fmt.Errorf("failed to write file: %w", err)
+ }
+
+ fmt.Println(color.Ize(color.Green, " [+] Exported to: "+filePath))
+ return nil
+}
+
diff --git a/osint/batch_test.go b/osint/batch_test.go
new file mode 100644
index 0000000..38f1329
--- /dev/null
+++ b/osint/batch_test.go
@@ -0,0 +1,460 @@
+package osint
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestBatchSatelliteStruct(t *testing.T) {
+ sat := BatchSatellite{
+ Name: "ISS (ZARYA)",
+ NORADID: "25544",
+ Country: "US",
+ ObjectType: "PAYLOAD",
+ }
+
+ if sat.Name != "ISS (ZARYA)" {
+ t.Errorf("Name = %q, want %q", sat.Name, "ISS (ZARYA)")
+ }
+ if sat.NORADID != "25544" {
+ t.Errorf("NORADID = %q, want %q", sat.NORADID, "25544")
+ }
+}
+
+func TestCompareSatellites(t *testing.T) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Sat1", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ OrbitInclination: 51.6,
+ MeanMotion: 15.5,
+ Eccentrcity: 0.0001,
+ },
+ },
+ {
+ Satellite: BatchSatellite{Name: "Sat2", NORADID: "12346"},
+ Success: true,
+ TLE: TLE{
+ OrbitInclination: 98.2,
+ MeanMotion: 14.2,
+ Eccentrcity: 0.0002,
+ },
+ },
+ {
+ Satellite: BatchSatellite{Name: "Sat3", NORADID: "12347"},
+ Success: false,
+ Error: fmt.Errorf("test error"),
+ },
+ }
+
+ comparison := CompareSatellites(results)
+
+ if comparison.Summary.TotalProcessed != 3 {
+ t.Errorf("TotalProcessed = %d, want %d", comparison.Summary.TotalProcessed, 3)
+ }
+ if comparison.Summary.Successful != 2 {
+ t.Errorf("Successful = %d, want %d", comparison.Summary.Successful, 2)
+ }
+ if comparison.Summary.Failed != 1 {
+ t.Errorf("Failed = %d, want %d", comparison.Summary.Failed, 1)
+ }
+
+ // Check average inclination
+ expectedAvg := (51.6 + 98.2) / 2.0
+ if comparison.Summary.AverageInclination != expectedAvg {
+ t.Errorf("AverageInclination = %.2f, want %.2f", comparison.Summary.AverageInclination, expectedAvg)
+ }
+
+ // Check average mean motion
+ expectedMM := (15.5 + 14.2) / 2.0
+ if comparison.Summary.AverageMeanMotion != expectedMM {
+ t.Errorf("AverageMeanMotion = %.4f, want %.4f", comparison.Summary.AverageMeanMotion, expectedMM)
+ }
+}
+
+func TestCompareSatellitesEmpty(t *testing.T) {
+ results := []BatchTLEResult{}
+ comparison := CompareSatellites(results)
+
+ if comparison.Summary.TotalProcessed != 0 {
+ t.Errorf("TotalProcessed = %d, want %d", comparison.Summary.TotalProcessed, 0)
+ }
+}
+
+func TestCompareSatellitesAllFailed(t *testing.T) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Sat1", NORADID: "12345"},
+ Success: false,
+ Error: fmt.Errorf("error 1"),
+ },
+ {
+ Satellite: BatchSatellite{Name: "Sat2", NORADID: "12346"},
+ Success: false,
+ Error: fmt.Errorf("error 2"),
+ },
+ }
+
+ comparison := CompareSatellites(results)
+
+ if comparison.Summary.Successful != 0 {
+ t.Errorf("Successful = %d, want %d", comparison.Summary.Successful, 0)
+ }
+ if comparison.Summary.Failed != 2 {
+ t.Errorf("Failed = %d, want %d", comparison.Summary.Failed, 2)
+ }
+}
+
+func TestExportBatchTLECSV(t *testing.T) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Test Sat", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ CommonName: "Test Satellite",
+ SatelliteCatalogNumber: 12345,
+ OrbitInclination: 51.6,
+ MeanMotion: 15.5,
+ Eccentrcity: 0.0001,
+ },
+ },
+ {
+ Satellite: BatchSatellite{Name: "Failed Sat", NORADID: "12346"},
+ Success: false,
+ Error: fmt.Errorf("test error"),
+ },
+ }
+
+ tempFile := filepath.Join(t.TempDir(), "batch_tle.csv")
+ err := exportBatchTLECSV(results, tempFile)
+ if err != nil {
+ t.Fatalf("exportBatchTLECSV() failed: %v", err)
+ }
+
+ // Verify file exists
+ if _, err := os.Stat(tempFile); os.IsNotExist(err) {
+ t.Fatal("CSV file was not created")
+ }
+
+ // Read and verify content
+ data, err := os.ReadFile(tempFile)
+ if err != nil {
+ t.Fatalf("Failed to read CSV file: %v", err)
+ }
+
+ content := string(data)
+ if !strings.Contains(content, "Test Sat") {
+ t.Error("CSV should contain satellite name")
+ }
+ if !strings.Contains(content, "12345") {
+ t.Error("CSV should contain NORAD ID")
+ }
+ if !strings.Contains(content, "Success") {
+ t.Error("CSV should contain success status")
+ }
+}
+
+func TestExportBatchTLEJSON(t *testing.T) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Test Sat", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ CommonName: "Test Satellite",
+ SatelliteCatalogNumber: 12345,
+ OrbitInclination: 51.6,
+ },
+ },
+ }
+
+ tempFile := filepath.Join(t.TempDir(), "batch_tle.json")
+ err := exportBatchTLEJSON(results, tempFile)
+ if err != nil {
+ t.Fatalf("exportBatchTLEJSON() failed: %v", err)
+ }
+
+ // Verify file exists and is valid JSON
+ data, err := os.ReadFile(tempFile)
+ if err != nil {
+ t.Fatalf("Failed to read JSON file: %v", err)
+ }
+
+ var result map[string]interface{}
+ if err := json.Unmarshal(data, &result); err != nil {
+ t.Fatalf("Failed to parse JSON: %v", err)
+ }
+
+ if result["batch_results"] == nil {
+ t.Error("JSON should contain batch_results")
+ }
+ if result["total_count"] != float64(1) {
+ t.Errorf("total_count = %v, want %d", result["total_count"], 1)
+ }
+}
+
+func TestExportBatchTLEText(t *testing.T) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Test Sat", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ CommonName: "Test Satellite",
+ SatelliteCatalogNumber: 12345,
+ OrbitInclination: 51.6,
+ MeanMotion: 15.5,
+ },
+ },
+ }
+
+ tempFile := filepath.Join(t.TempDir(), "batch_tle.txt")
+ err := exportBatchTLEText(results, tempFile)
+ if err != nil {
+ t.Fatalf("exportBatchTLEText() failed: %v", err)
+ }
+
+ // Verify file exists
+ content, err := os.ReadFile(tempFile)
+ if err != nil {
+ t.Fatalf("Failed to read text file: %v", err)
+ }
+
+ text := string(content)
+ if !strings.Contains(text, "Test Sat") {
+ t.Error("Text file should contain satellite name")
+ }
+ if !strings.Contains(text, "12345") {
+ t.Error("Text file should contain NORAD ID")
+ }
+ if !strings.Contains(text, "Success") {
+ t.Error("Text file should contain success status")
+ }
+}
+
+func TestExportBatchComparisonCSV(t *testing.T) {
+ comparison := BatchComparisonResult{
+ Summary: BatchSummary{
+ TotalProcessed: 2,
+ Successful: 1,
+ Failed: 1,
+ AverageInclination: 51.6,
+ AverageMeanMotion: 15.5,
+ },
+ Results: []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Sat1", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ OrbitInclination: 51.6,
+ MeanMotion: 15.5,
+ Eccentrcity: 0.0001,
+ },
+ },
+ {
+ Satellite: BatchSatellite{Name: "Sat2", NORADID: "12346"},
+ Success: false,
+ Error: fmt.Errorf("test error"),
+ },
+ },
+ }
+
+ tempFile := filepath.Join(t.TempDir(), "comparison.csv")
+ err := exportBatchComparisonCSV(comparison, tempFile)
+ if err != nil {
+ t.Fatalf("exportBatchComparisonCSV() failed: %v", err)
+ }
+
+ // Verify file exists
+ if _, err := os.Stat(tempFile); os.IsNotExist(err) {
+ t.Fatal("CSV file was not created")
+ }
+
+ // Read and verify
+ data, err := os.ReadFile(tempFile)
+ if err != nil {
+ t.Fatalf("Failed to read CSV file: %v", err)
+ }
+
+ content := string(data)
+ if !strings.Contains(content, "Total Processed") {
+ t.Error("CSV should contain summary")
+ }
+ if !strings.Contains(content, "Sat1") {
+ t.Error("CSV should contain satellite names")
+ }
+}
+
+func TestExportBatchComparisonJSON(t *testing.T) {
+ comparison := BatchComparisonResult{
+ Summary: BatchSummary{
+ TotalProcessed: 1,
+ Successful: 1,
+ },
+ Results: []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Sat1", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{OrbitInclination: 51.6},
+ },
+ },
+ }
+
+ tempFile := filepath.Join(t.TempDir(), "comparison.json")
+ err := exportBatchComparisonJSON(comparison, tempFile)
+ if err != nil {
+ t.Fatalf("exportBatchComparisonJSON() failed: %v", err)
+ }
+
+ // Verify file exists and is valid JSON
+ data, err := os.ReadFile(tempFile)
+ if err != nil {
+ t.Fatalf("Failed to read JSON file: %v", err)
+ }
+
+ var result map[string]interface{}
+ if err := json.Unmarshal(data, &result); err != nil {
+ t.Fatalf("Failed to parse JSON: %v", err)
+ }
+
+ if result["summary"] == nil {
+ t.Error("JSON should contain summary")
+ }
+ if result["results"] == nil {
+ t.Error("JSON should contain results")
+ }
+}
+
+func TestExportBatchComparisonText(t *testing.T) {
+ comparison := BatchComparisonResult{
+ Summary: BatchSummary{
+ TotalProcessed: 1,
+ Successful: 1,
+ AverageInclination: 51.6,
+ },
+ Results: []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Sat1", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{OrbitInclination: 51.6},
+ },
+ },
+ }
+
+ tempFile := filepath.Join(t.TempDir(), "comparison.txt")
+ err := exportBatchComparisonText(comparison, tempFile)
+ if err != nil {
+ t.Fatalf("exportBatchComparisonText() failed: %v", err)
+ }
+
+ // Verify file exists
+ content, err := os.ReadFile(tempFile)
+ if err != nil {
+ t.Fatalf("Failed to read text file: %v", err)
+ }
+
+ text := string(content)
+ if !strings.Contains(text, "Sat1") {
+ t.Error("Text file should contain satellite name")
+ }
+ if !strings.Contains(text, "Summary") {
+ t.Error("Text file should contain summary section")
+ }
+}
+
+func TestBatchTLEResultStruct(t *testing.T) {
+ result := BatchTLEResult{
+ Satellite: BatchSatellite{
+ Name: "Test",
+ NORADID: "12345",
+ Country: "US",
+ ObjectType: "PAYLOAD",
+ },
+ Success: true,
+ TLE: TLE{
+ SatelliteCatalogNumber: 12345,
+ },
+ }
+
+ if !result.Success {
+ t.Error("Success should be true")
+ }
+ if result.Satellite.NORADID != "12345" {
+ t.Errorf("NORADID = %q, want %q", result.Satellite.NORADID, "12345")
+ }
+}
+
+func TestBatchSummaryStruct(t *testing.T) {
+ summary := BatchSummary{
+ TotalProcessed: 10,
+ Successful: 8,
+ Failed: 2,
+ AverageInclination: 51.6,
+ AverageMeanMotion: 15.5,
+ LowestAltitude: 400.0,
+ HighestAltitude: 500.0,
+ }
+
+ if summary.TotalProcessed != 10 {
+ t.Errorf("TotalProcessed = %d, want %d", summary.TotalProcessed, 10)
+ }
+ if summary.Successful != 8 {
+ t.Errorf("Successful = %d, want %d", summary.Successful, 8)
+ }
+ if summary.AverageInclination != 51.6 {
+ t.Errorf("AverageInclination = %.2f, want %.2f", summary.AverageInclination, 51.6)
+ }
+}
+
+// Benchmark tests
+func BenchmarkCompareSatellites(b *testing.B) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Sat1", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ OrbitInclination: 51.6,
+ MeanMotion: 15.5,
+ Eccentrcity: 0.0001,
+ },
+ },
+ {
+ Satellite: BatchSatellite{Name: "Sat2", NORADID: "12346"},
+ Success: true,
+ TLE: TLE{
+ OrbitInclination: 98.2,
+ MeanMotion: 14.2,
+ Eccentrcity: 0.0002,
+ },
+ },
+ }
+
+ for i := 0; i < b.N; i++ {
+ CompareSatellites(results)
+ }
+}
+
+func BenchmarkExportBatchTLEJSON(b *testing.B) {
+ results := []BatchTLEResult{
+ {
+ Satellite: BatchSatellite{Name: "Test", NORADID: "12345"},
+ Success: true,
+ TLE: TLE{
+ CommonName: "Test Satellite",
+ SatelliteCatalogNumber: 12345,
+ OrbitInclination: 51.6,
+ MeanMotion: 15.5,
+ },
+ },
+ }
+
+ tempDir := b.TempDir()
+ for i := 0; i < b.N; i++ {
+ file := filepath.Join(tempDir, fmt.Sprintf("test_%d.json", i))
+ exportBatchTLEJSON(results, file)
+ }
+}
+
diff --git a/osint/errors.go b/osint/errors.go
new file mode 100644
index 0000000..07b2f70
--- /dev/null
+++ b/osint/errors.go
@@ -0,0 +1,388 @@
+package osint
+
+import (
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/TwiN/go-color"
+)
+
+// ErrorCode represents a unique error code for troubleshooting.
+type ErrorCode string
+
+const (
+ // Authentication errors (1000-1099)
+ ErrCodeAuthFailed ErrorCode = "AUTH-1001"
+ ErrCodeAuthCredentials ErrorCode = "AUTH-1002"
+ ErrCodeAuthConnection ErrorCode = "AUTH-1003"
+ ErrCodeAuthCookieJar ErrorCode = "AUTH-1004"
+
+ // API errors (1100-1199)
+ ErrCodeAPIRequestFailed ErrorCode = "API-1101"
+ ErrCodeAPIResponseFailed ErrorCode = "API-1102"
+ ErrCodeAPIParseFailed ErrorCode = "API-1103"
+ ErrCodeAPINoData ErrorCode = "API-1104"
+ ErrCodeAPIInvalidEndpoint ErrorCode = "API-1105"
+
+ // Input validation errors (1200-1299)
+ ErrCodeInputEmpty ErrorCode = "INPUT-1201"
+ ErrCodeInputInvalid ErrorCode = "INPUT-1202"
+ ErrCodeInputOutOfRange ErrorCode = "INPUT-1203"
+ ErrCodeInputFormat ErrorCode = "INPUT-1204"
+
+ // TLE errors (1300-1399)
+ ErrCodeTLEInvalidFormat ErrorCode = "TLE-1301"
+ ErrCodeTLEParseFailed ErrorCode = "TLE-1302"
+ ErrCodeTLEInsufficientData ErrorCode = "TLE-1303"
+ ErrCodeTLEChecksumFailed ErrorCode = "TLE-1304"
+
+ // File errors (1400-1499)
+ ErrCodeFileNotFound ErrorCode = "FILE-1401"
+ ErrCodeFileReadFailed ErrorCode = "FILE-1402"
+ ErrCodeFilePathInvalid ErrorCode = "FILE-1403"
+ ErrCodeFilePermission ErrorCode = "FILE-1404"
+
+ // Satellite selection errors (1500-1599)
+ ErrCodeSatNotFound ErrorCode = "SAT-1501"
+ ErrCodeSatInvalidNORAD ErrorCode = "SAT-1502"
+ ErrCodeSatNoResults ErrorCode = "SAT-1503"
+
+ // Network errors (1600-1699)
+ ErrCodeNetworkTimeout ErrorCode = "NET-1601"
+ ErrCodeNetworkUnreachable ErrorCode = "NET-1602"
+ ErrCodeNetworkDNS ErrorCode = "NET-1603"
+)
+
+// AppError represents a structured application error with code, message, and suggestions.
+type AppError struct {
+ Code ErrorCode
+ Message string
+ Context string
+ Suggestions []string
+ OriginalErr error
+}
+
+// Error implements the error interface.
+func (e *AppError) Error() string {
+ if e.OriginalErr != nil {
+ return fmt.Sprintf("%s: %s (%s)", e.Code, e.Message, e.OriginalErr.Error())
+ }
+ return fmt.Sprintf("%s: %s", e.Code, e.Message)
+}
+
+// Display formats and displays the error with suggestions.
+func (e *AppError) Display() {
+ fmt.Println(color.Ize(color.Red, fmt.Sprintf(" [!] ERROR [%s]: %s", e.Code, e.Message)))
+
+ if e.Context != "" {
+ fmt.Println(color.Ize(color.Yellow, fmt.Sprintf(" Context: %s", e.Context)))
+ }
+
+ if len(e.Suggestions) > 0 {
+ fmt.Println(color.Ize(color.Cyan, " Suggestions:"))
+ for i, suggestion := range e.Suggestions {
+ fmt.Println(color.Ize(color.Cyan, fmt.Sprintf(" %d. %s", i+1, suggestion)))
+ }
+ }
+
+ if e.OriginalErr != nil {
+ fmt.Println(color.Ize(color.Gray, fmt.Sprintf(" Technical details: %v", e.OriginalErr)))
+ }
+}
+
+// NewAppError creates a new AppError with the given code and message.
+func NewAppError(code ErrorCode, message string) *AppError {
+ return &AppError{
+ Code: code,
+ Message: message,
+ Suggestions: getDefaultSuggestions(code),
+ }
+}
+
+// NewAppErrorWithContext creates a new AppError with context.
+func NewAppErrorWithContext(code ErrorCode, message, context string) *AppError {
+ return &AppError{
+ Code: code,
+ Message: message,
+ Context: context,
+ Suggestions: getDefaultSuggestions(code),
+ }
+}
+
+// NewAppErrorWithErr wraps an original error in an AppError.
+func NewAppErrorWithErr(code ErrorCode, message string, err error) *AppError {
+ return &AppError{
+ Code: code,
+ Message: message,
+ OriginalErr: err,
+ Suggestions: getDefaultSuggestions(code),
+ }
+}
+
+// getDefaultSuggestions returns default suggestions based on error code.
+func getDefaultSuggestions(code ErrorCode) []string {
+ suggestions := map[ErrorCode][]string{
+ // Authentication errors
+ ErrCodeAuthFailed: {
+ "Verify your Space-Track credentials in the .env file",
+ "Check if your account is active at space-track.org",
+ "Ensure SPACE_TRACK_USERNAME and SPACE_TRACK_PASSWORD are set correctly",
+ },
+ ErrCodeAuthCredentials: {
+ "Check your .env file for SPACE_TRACK_USERNAME and SPACE_TRACK_PASSWORD",
+ "Verify credentials are not expired or changed",
+ "Try logging in manually at space-track.org to verify your account",
+ },
+ ErrCodeAuthConnection: {
+ "Check your internet connection",
+ "Verify space-track.org is accessible",
+ "Check if a firewall or proxy is blocking the connection",
+ "Try again in a few moments - the service may be temporarily unavailable",
+ },
+ ErrCodeAuthCookieJar: {
+ "Restart the application",
+ "Check system permissions for cookie storage",
+ },
+
+ // API errors
+ ErrCodeAPIRequestFailed: {
+ "Check your internet connection",
+ "Verify the API endpoint is correct",
+ "Try again in a few moments",
+ "Check if you're authenticated (run login first)",
+ },
+ ErrCodeAPIResponseFailed: {
+ "The API returned an error response",
+ "Check if your query parameters are valid",
+ "Verify you have permission to access this data",
+ "Try with different search criteria",
+ },
+ ErrCodeAPIParseFailed: {
+ "The API response format may have changed",
+ "Check if the data structure is correct",
+ "Try refreshing the data",
+ },
+ ErrCodeAPINoData: {
+ "Try adjusting your search criteria",
+ "Verify the satellite NORAD ID exists",
+ "Check if the satellite is still active",
+ },
+ ErrCodeAPIInvalidEndpoint: {
+ "Verify the endpoint URL is correct",
+ "Check API documentation for valid endpoints",
+ },
+
+ // Input validation errors
+ ErrCodeInputEmpty: {
+ "Please provide a value for this field",
+ "Check if you accidentally pressed Enter without entering data",
+ },
+ ErrCodeInputInvalid: {
+ "Enter a valid numeric value",
+ "Remove any non-numeric characters (except decimal point and minus sign)",
+ "Check the expected format for this input",
+ },
+ ErrCodeInputOutOfRange: {
+ "Check the valid range for this input",
+ "Latitude: -90 to 90",
+ "Longitude: -180 to 180",
+ "Altitude: 0 to 8848 meters (Mount Everest)",
+ },
+ ErrCodeInputFormat: {
+ "Verify the input format matches the expected pattern",
+ "Check for typos or extra characters",
+ },
+
+ // TLE errors
+ ErrCodeTLEInvalidFormat: {
+ "Ensure TLE data has exactly 2 lines",
+ "Line 1 must start with '1 '",
+ "Line 2 must start with '2 '",
+ "Each line should be 69 characters long",
+ },
+ ErrCodeTLEParseFailed: {
+ "Verify the TLE data is complete and not corrupted",
+ "Check if all required fields are present",
+ "Ensure the TLE format follows the standard specification",
+ },
+ ErrCodeTLEInsufficientData: {
+ "Ensure you have both TLE lines",
+ "Check if the data was truncated",
+ "Verify the source of your TLE data",
+ },
+ ErrCodeTLEChecksumFailed: {
+ "The TLE checksum validation failed",
+ "Verify the TLE data is not corrupted",
+ "Try fetching fresh TLE data from Space-Track",
+ },
+
+ // File errors
+ ErrCodeFileNotFound: {
+ "Verify the file path is correct",
+ "Check if the file exists at the specified location",
+ "Use an absolute path or ensure the file is in the current directory",
+ },
+ ErrCodeFileReadFailed: {
+ "Check file permissions",
+ "Ensure the file is not locked by another process",
+ "Verify the file is not corrupted",
+ },
+ ErrCodeFilePathInvalid: {
+ "Use a valid file path",
+ "Avoid directory traversal patterns (../)",
+ "Check for invalid characters in the path",
+ },
+ ErrCodeFilePermission: {
+ "Check file and directory permissions",
+ "Ensure you have read access to the file",
+ "On Unix systems, check with: ls -l NORAD ID: `) + builder.WriteString(fmt.Sprintf("%d", data.SatelliteInfo.Satid)) + builder.WriteString(`
+Positions: `) + builder.WriteString(fmt.Sprintf("%d", len(data.Positions))) + builder.WriteString(`
+Click on markers to see details
+