package helper import ( "fmt" "log" "math" "os" "os/signal" "strings" "time" "github.com/go-vgo/robotgo" "gopkg.in/yaml.v3" ) type Point struct { X int Y int } func (p Point) String() string { return fmt.Sprintf("(%v, %v)", p.X, p.Y) } func (p Point) Click(button ...string) { buttonToClick := "left" if len(button) > 0 && (button[0] == "left" || button[0] == "right") { buttonToClick = button[0] } robotgo.Move(p.X, p.Y) robotgo.Click(buttonToClick, false) } func (p Point) RightClick() { p.Click("right") } func (p1 *Point) GetDistance(p2 *Point) float64 { return math.Sqrt(math.Pow(float64(p1.X-p2.X), float64(2)) + math.Pow(float64(p1.Y-p2.Y), float64(2))) } func (p0 *Point) GetDistanceFromClosest(pp ...*Point) float64 { var minDistance float64 for index, p := range pp { if distance := p.GetDistance(p0); index == 0 || distance < minDistance { minDistance = distance } } return minDistance } type PointGroup struct { Name string Points []*Point } type SavedGroups struct { Groups []PointGroup } func getSavedGroups(yamlFile string) *SavedGroups { s := SavedGroups{} inputFile, err := os.ReadFile(yamlFile) if err == nil { // No yaml file found err = yaml.Unmarshal(inputFile, &s) if err != nil { log.Fatalf("%v - Exiting", err) } } for index, g := range s.Groups { if index == 0 { fmt.Println("Available groups:") } fmt.Printf("%v. %v\n", index+1, g.Name) } return &s } func addSavedGroup(s *SavedGroups) { g := PointGroup{} var name string var points int for { name = "" fmt.Print("If you want to add a new group of locations, enter a name. Press enter to skip. ") fmt.Scanln(&name) if name == "" { break } fmt.Printf("How many positions are there in group %v? ", name) _, err := fmt.Scanln(&points) if err != nil { fmt.Println("I haven't understood, try again...") var discard string fmt.Scanln(&discard) continue } for i := 0; i < points; i++ { fmt.Printf("Please move your mouse into position %v of %v (group %v)\n", i+1, points, name) time.Sleep(2 * time.Second) x, y := robotgo.GetMousePos() fmt.Printf("Added point (x=%v, y=%v) to %v\n", x, y, name) p := Point{X: x, Y: y} g.Points = append(g.Points, &p) } g.Name = name s.Groups = append(s.Groups, g) } } func storeSavedGroups(s *SavedGroups, yamlFile string) { d, err := yaml.Marshal(s) if err != nil { log.Fatalf("error: %v", err) } err = os.WriteFile(yamlFile, d, 0644) if err != nil { log.Fatalf("error: %v", err) } } func AbsInt(i int) int { if i > 0 { return i } return -i } func Oscillator(amplitude int) chan int { amplitude-- c := make(chan int, 1) i := amplitude go func() { for { c <- AbsInt((i%(amplitude*2))-amplitude) + 1 i++ if i == amplitude*2 { i = 0 } } }() return c } func StringOscillator(s string, amplitude int) chan string { c := make(chan string) o := Oscillator(amplitude) go func() { for { c <- strings.Repeat(s, <-o) } }() return c } func press(key string) { robotgo.KeyTap(key) } func keyboardWrite(s string) { robotgo.TypeStr(s) robotgo.MilliSleep(100) } func ClickRepeatedly(s *SavedGroups, c chan os.Signal) { for index, g := range s.Groups { fmt.Printf("- Press %v to select %v\n", index+1, g.Name) } var choice int for choice < 1 { fmt.Print("Your selection: ") _, err := fmt.Scanln(&choice) if err != nil { fmt.Println("I haven't understood, try again...") var discard string fmt.Scanln(&discard) continue } if choice > len(s.Groups) { choice = 0 } } selection := s.Groups[choice-1] fmt.Printf("You selected %v. I'll be clicking in the following positions:\n", selection.Name) for index, p := range selection.Points { fmt.Printf("%v - %v\n", index+1, p) } o := StringOscillator(".", 15) var t int var minDistance float64 for { for _, p := range selection.Points { p.Click() time.Sleep(100 * time.Millisecond) } fmt.Println(<-o) time.Sleep(500 * time.Millisecond) t = 0 for t = 0; t < 10; t++ { x, y := robotgo.GetMousePos() currentPosition := Point{X: x, Y: y} minDistance = currentPosition.GetDistanceFromClosest(selection.Points...) if minDistance <= 50 { break } fmt.Printf("%v You moved the mouse (distance %.2f), waiting 2 seconds (%v of 10)\n", strings.Repeat("_", 10-t), minDistance, t+1) time.Sleep(2 * time.Second) } if t >= 10 { fmt.Println("You stayed away too long, exiting...") c <- os.Interrupt return } } } func cleanUp(c chan os.Signal) chan int { r := make(chan int) go func() { signal := <-c fmt.Printf("Got signal %v, cleaning up...\n", signal) r <- 1 }() return r } func interactWithUser(s *SavedGroups, yamlFile string, c chan os.Signal) { addSavedGroup(s) storeSavedGroups(s, yamlFile) ClickRepeatedly(s, c) } func RunAutoClicker() { c := make(chan os.Signal, 1) quit := cleanUp(c) signal.Notify(c, os.Interrupt) yamlFile := "SavedGroups.yaml" s := getSavedGroups(yamlFile) go interactWithUser(s, yamlFile, c) if <-quit == 1 { fmt.Println("Exiting...") } else { fmt.Println("Unknown error...") } }