commit 950aaffcc763dbcff014fcbaa7d8214deb8e6e70 Author: Avii Date: Fri Jan 2 05:00:07 2026 +0100 Inital commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d017436 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +./autobrew diff --git a/action/action.go b/action/action.go new file mode 100644 index 0000000..78beb56 --- /dev/null +++ b/action/action.go @@ -0,0 +1,59 @@ +package action + +import ( + "autobrew/utils" + "fmt" +) + +type StepAction int + +const ( + Prepare StepAction = iota + Base + AddToCauldron + GrindIngredients + Boil + BoilBellows + PourPhial + Distill + GrindPotion +) + +func (e *StepAction) UnmarshalText(text []byte) (err error) { + name := string(text) + // *e, err = ParseEngine(name) + fmt.Println(name) + return +} + +func (a *StepAction) UnmarshalYAML(unmarshal func(any) error) error { + var s string + err := unmarshal(&s) + if err != nil { + return err + } + s = utils.Strip(s) + switch s { + case "Prepare": + *a = Prepare + case "Base": + *a = Base + case "AddToCauldron": + *a = AddToCauldron + case "GrindIngredients": + *a = GrindIngredients + case "Boil": + *a = Boil + case "BoilBellows": + *a = BoilBellows + case "PourPhial": + *a = PourPhial + case "Distill": + *a = Distill + case "GrindPotion": + *a = GrindPotion + default: + return fmt.Errorf("Invalid Action: %s", s) + } + return nil +} diff --git a/alchemy/alchemy.go b/alchemy/alchemy.go new file mode 100644 index 0000000..582c66c --- /dev/null +++ b/alchemy/alchemy.go @@ -0,0 +1,443 @@ +package alchemy + +import ( + "autobrew/action" + "autobrew/container" + "autobrew/ingredient" + "autobrew/recipe" + "autobrew/utils" + "bytes" + "fmt" + "image" + "image/png" + "os" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/MaxHalford/halfgone" + "github.com/bendahl/uinput" + "github.com/otiai10/gosseract/v2" + "github.com/vova616/screenshot" + "github.com/yassinebenaid/godump" +) + +type Alchemy struct { + keyboard uinput.Keyboard + mouse uinput.Mouse + searchRect image.Rectangle + save bool +} + +func NewAlchemy(mouse uinput.Mouse, keyboard uinput.Keyboard, searchRect image.Rectangle, save bool) *Alchemy { + return &Alchemy{ + mouse: mouse, + keyboard: keyboard, + searchRect: searchRect, + save: save, + } +} + +func (a Alchemy) findString(word string) (out gosseract.BoundingBox, err error) { + img, err := screenshot.CaptureRect(a.searchRect) + utils.Check(err) + + gray := halfgone.ImageToGray(img) + gray = halfgone.ThresholdDitherer{Threshold: 85}.Apply(gray) + + buf := new(bytes.Buffer) + err = png.Encode(buf, gray) + utils.Check(err) + + client := gosseract.NewClient() + defer client.Close() + + client.SetImageFromBytes(buf.Bytes()) // use from screen frame + + if a.save { + path1 := filepath.Join(".", "output.png") + err = os.WriteFile(path1, buf.Bytes(), 0644) + utils.Check(err) + } + + bounding_boxes, err := client.GetBoundingBoxes(2) + utils.Check(err) + + index := slices.IndexFunc(bounding_boxes, func(e gosseract.BoundingBox) bool { + a := strings.ToLower(e.Word) + b := strings.ToLower(word) + return strings.Contains(a, b) + }) + + if index < 0 { + text, err := client.Text() + utils.Check(err) + // godump.Dump(bounding_boxes) + godump.Dump(text) + return out, fmt.Errorf("%s not found\n", word) + } + out = bounding_boxes[index] + + return +} + +func (a Alchemy) sleep(msec time.Duration) { + time.Sleep(time.Duration(msec * time.Millisecond)) +} + +func (a Alchemy) use() { + fmt.Println("Pressing E") + a.keyboard.KeyPress(uinput.KeyE) + a.sleep(200) +} + +func (a Alchemy) hold() { + fmt.Println("Holding E") + a.keyboard.KeyDown(18) + a.sleep(1000) + a.keyboard.KeyUp(18) + a.sleep(200) +} + +func (a Alchemy) lookAt(str string) { + a.mouse.Move(-1200, -550) + a.sleep(200) + // fmt.Printf("Looking at %s\n", str) + switch strings.ToLower(str) { + case "water": + a.mouse.Move(0, 150) + case "wine": + a.mouse.Move(100, 150) + case "oil": + a.mouse.Move(200, 150) + case "spirits": + a.mouse.Move(300, 150) + case "phial": + a.mouse.Move(105, 320) + a.sleep(2500) // make sure we're actually looking at it + case "still": + a.mouse.Move(200, 460) + case "dish": + a.mouse.Move(600, 600) + case "mortar": + a.mouse.Move(850, 500) + case "cauldron": + a.mouse.Move(530, 300) + case "topshelf_0": + a.mouse.Move(1000, 150) + case "topshelf_1": + a.mouse.Move(900, 150) + case "topshelf_2": + a.mouse.Move(760, 150) + case "bottomshelf_0": + a.mouse.Move(980, 350) + case "bottomshelf_1": + a.mouse.Move(900, 350) + case "bottomshelf_2": + a.mouse.Move(780, 350) + } + a.sleep(100) +} + +func (a Alchemy) OpenInventory() { + fmt.Println("Opening Inventory") + a.keyboard.KeyPress(uinput.KeyI) + a.sleep(400) +} + +func (a Alchemy) SelectIngredient(str ingredient.Ingredient) { + rect, err := a.findString(str.Name()) + utils.Check(err) + + x := rect.Box.Min.X + (rect.Box.Bounds().Size().X / 2) + y := rect.Box.Min.Y + (rect.Box.Bounds().Size().Y / 2) + + a.mouse.Move(-2560, -1440) + a.sleep(100) + + // fmt.Printf("Selecting %s at %dx%d\n", str.Name(), x, y) + + a.mouse.Move(int32(x), int32(y)) + a.sleep(50) + a.mouse.LeftClick() + a.mouse.LeftClick() + a.sleep(50) +} + +func (a Alchemy) CloseInventory() { + fmt.Println("Closing Inventory") + a.keyboard.KeyPress(uinput.KeyEsc) + a.sleep(300) +} + +func (a Alchemy) ClosePopup() { + fmt.Println("Closing Popup") + a.keyboard.KeyPress(uinput.KeyEsc) + a.sleep(300) +} + +func (a Alchemy) PourBase(i ingredient.Ingredient) { + a.lookAt(i.Name()) + a.sleep(100) + a.use() + a.sleep(7980) +} + +func (a Alchemy) GrabIngredient(i ingredient.Ingredient, shelf []ingredient.Ingredient) { + + // fmt.Printf("Looking for %s\n", i.Name()) + + bottomShelf := utils.Filter(shelf, func(i ingredient.Ingredient) bool { + return i.BottomShelf() + }) + + topShelf := utils.Filter(shelf, func(i ingredient.Ingredient) bool { + return !i.BottomShelf() + }) + + topIndex := slices.Index(topShelf[:], i) + + if topIndex > -1 { + // fmt.Printf("Found %s on the top shelf at index %d\n", i.Name(), topIndex) + a.sleep(1000) + switch topIndex { + case 0: + a.lookAt("topshelf_0") + case 1: + a.lookAt("topshelf_1") + case 2: + a.lookAt("topshelf_2") + } + } else { + bottomIndex := slices.Index(bottomShelf[:], i) + if bottomIndex == -1 { + return + } + + // fmt.Printf("Found %s on the bottom shelf at index %d\n", i.Name(), bottomIndex) + switch bottomIndex { + case 0: + a.lookAt("bottomshelf_0") + case 1: + a.lookAt("bottomshelf_1") + case 2: + a.lookAt("bottomshelf_2") + } + } + + a.sleep(500) + a.use() + a.sleep(5000) +} + +func (a Alchemy) DropIn(c container.Container) { + switch c { + case container.Cauldron: + a.lookAt("cauldron") + case container.Dish: + a.lookAt("dish") + case container.Mortar: + a.lookAt("mortar") + default: + panic(fmt.Sprintf("unexpected container.Container: %#v", c)) + } + a.sleep(100) + a.use() + a.sleep(5500) +} + +func (a Alchemy) Grind() { + a.lookAt("mortar") + a.use() + a.sleep(10000) +} + +func (a Alchemy) Boil(turns int) { + a.lookAt("cauldron") + + a.sleep(100) + a.keyboard.KeyPress(uinput.KeyX) + a.sleep(5000) + + for range turns { + // fmt.Printf("Boiling for %d turns\n", turns-i) + a.sleep(9000) + } + a.keyboard.KeyPress(uinput.KeyX) + a.sleep(5000) +} + +func (a Alchemy) BoilBellows(turns int) { + // var duration time.Duration = 10 + a.lookAt("cauldron") + + a.sleep(100) + a.keyboard.KeyPress(uinput.KeyX) + a.sleep(5000) // 4+1 seconds to lower the cauldron and one for pre-boil + + for range turns { + // fmt.Printf("Bellow Boiling for %d turns\n", turns-i) + + for range 18 * 2 { + a.keyboard.KeyPress(uinput.KeyQ) // tap q every 500ms + a.sleep(250) // boil time, 18*500 makes 9000 again + } + } + + a.sleep(2000) + + a.keyboard.KeyPress(uinput.KeyX) + a.sleep(5000) // 5 seconds to pull it back up and stop animating +} + +func (a Alchemy) PreparePhial() { + a.lookAt("phial") + a.use() + a.sleep(2000) +} + +func (a Alchemy) Distill() { + a.lookAt("still") + a.hold() + a.sleep(16000) +} + +func (a Alchemy) PourOut() { + a.lookAt("cauldron") + a.hold() + a.sleep(8000) +} + +func (a Alchemy) GrindOut() { + a.lookAt("mortar") + a.hold() + a.sleep(15000) +} + +func (a Alchemy) innerBrew(r recipe.Recipe) { + for _, step := range r.Steps { + switch step.Action { + case action.Prepare: + ing := utils.DedupeSlice(step.Ingredients) + base := utils.Filter(ing, func(i ingredient.Ingredient) bool { + return i.IngredientType == ingredient.Base + }) + if len(base) == 0 { + panic("Recipe is missing a base") + } + + a.OpenInventory() + for _, i := range ing { + if i.IngredientType != ingredient.Base { + // r.Ingredients = append(r.Ingredients, i) + a.SelectIngredient(i) + } + } + a.CloseInventory() + + a.PourBase(base[0]) + + case action.AddToCauldron: + for _, i := range step.Ingredients { + a.GrabIngredient(i, r.Ingredients) + a.DropIn(container.Cauldron) + } + case action.Boil: + for _, i := range step.Ingredients { + a.GrabIngredient(i, r.Ingredients) + a.DropIn(container.Cauldron) + } + + a.Boil(step.Turns) + + case action.BoilBellows: + for _, i := range step.Ingredients { + a.GrabIngredient(i, r.Ingredients) + a.DropIn(container.Cauldron) + } + + a.BoilBellows(step.Turns) + + case action.GrindIngredients: + for _, i := range step.Ingredients { + a.GrabIngredient(i, r.Ingredients) + a.DropIn(container.Mortar) + } + + a.Grind() + a.DropIn(container.Cauldron) + + // finishers + case action.PourPhial: + a.PreparePhial() + a.PourOut() + + case action.Distill: + a.PreparePhial() + a.Distill() + + case action.GrindPotion: + a.GrindOut() + + default: + panic(fmt.Sprintf("unexpected main.StepAction: %#v", step.Action)) + } + } +} + +func (a Alchemy) Brew(book map[string][]recipe.Step, name string, amount int) { + r := recipe.Recipe{ + Steps: book[name], + } + r.LoadIngredients() + + ingredients := make(map[string]int) + + for _, i := range r.Ingredients { + ingredients[i.Name()]++ + } + + formatted := make([]string, 0) + + for k, v := range ingredients { + formatted = append(formatted, fmt.Sprintf("%dx %s", v, k)) + } + + fmt.Printf("%s\n\nIngredients:\n\t%s\n\n", name, strings.Join(formatted, "\n\t")) + + a.sleep(1000) + + totalStartTime := time.Now() + if amount == -1 { + i := 0 + for { + fmt.Printf("Start brewing %s in 3\n", name) + a.sleep(1000) + fmt.Printf("Start brewing %s in 2\n", name) + a.sleep(1000) + fmt.Printf("Start brewing %s in 1\n", name) + a.sleep(1000) + + startTime := time.Now() + a.innerBrew(r) + fmt.Printf("Batch %d took %s, total %s\n", i+1, time.Since(startTime), time.Since(totalStartTime)) + a.ClosePopup() + i++ + } + } + + for i := range amount { + fmt.Printf("Start brewing %s in 3\n", name) + a.sleep(1000) + fmt.Printf("Start brewing %s in 2\n", name) + a.sleep(1000) + fmt.Printf("Start brewing %s in 1\n", name) + a.sleep(1000) + + startTime := time.Now() + a.innerBrew(r) + fmt.Printf("Batch %d took %s, total %s\n", i+1, time.Since(startTime), time.Since(totalStartTime)) + a.ClosePopup() + } +} diff --git a/autobrew b/autobrew new file mode 100755 index 0000000..2583f71 Binary files /dev/null and b/autobrew differ diff --git a/container/container.go b/container/container.go new file mode 100644 index 0000000..d8e20d8 --- /dev/null +++ b/container/container.go @@ -0,0 +1,9 @@ +package container + +type Container int + +const ( + Cauldron Container = iota + Mortar + Dish +) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6233796 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module autobrew + +go 1.25.5 + +require ( + github.com/MaxHalford/halfgone v0.0.0-20171017091812-482157b86ccb + github.com/ThomasObenaus/go-conf v0.1.3 + github.com/bendahl/uinput v1.7.0 + github.com/goccy/go-yaml v1.19.1 + github.com/otiai10/gosseract/v2 v2.4.1 + github.com/vova616/screenshot v0.0.0-20220801010501-56c10359473c + github.com/yassinebenaid/godump v0.11.1 +) + +require ( + github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 // indirect + github.com/fsnotify/fsnotify v1.4.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/magiconair/properties v1.8.1 // indirect + github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/pelletier/go-toml v1.2.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/afero v1.1.2 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/jwalterweatherman v1.0.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/viper v1.7.1 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect + golang.org/x/text v0.3.3 // indirect + gopkg.in/ini.v1 v1.51.0 // indirect + gopkg.in/yaml.v2 v2.2.4 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ebe061a --- /dev/null +++ b/go.sum @@ -0,0 +1,344 @@ +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.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/MaxHalford/halfgone v0.0.0-20171017091812-482157b86ccb h1:YQ+d0g0P0F/06oDoeEgDHeZCIrnKgLxXcqYOpe8sTuU= +github.com/MaxHalford/halfgone v0.0.0-20171017091812-482157b86ccb/go.mod h1:J86XzS1wgzJPjpQmpriJ+SetP17JSQUd9l+HWQK86jA= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ThomasObenaus/go-conf v0.1.3 h1:Kk9qpmOhlFd6SChVufVXKvIncmrIVjHSUJqRO2odC4M= +github.com/ThomasObenaus/go-conf v0.1.3/go.mod h1:J9r5Rh/fpJ932jKYf32W5UOmTOJO/n1Vh3zSv5qmhUA= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg= +github.com/bendahl/uinput v1.7.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE= +github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +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/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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/otiai10/gosseract/v2 v2.4.1 h1:G8AyBpXEeSlcq8TI85LH/pM5SXk8Djy2GEXisgyblRw= +github.com/otiai10/gosseract/v2 v2.4.1/go.mod h1:1gNWP4Hgr2o7yqWfs6r5bZxAatjOIdqWxJLWsTsembk= +github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= +github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.20.0/go.mod h1:IzD0RJ65iWH0w97OQQebJEvTZYvsCUm9WVLWBQrJRjo= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk= +github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/vova616/screenshot v0.0.0-20220801010501-56c10359473c h1:mqgl+NQgzZ30EX7oA3uNIlPn6dAgG6sThBiUodUzZpU= +github.com/vova616/screenshot v0.0.0-20220801010501-56c10359473c/go.mod h1:gjlNhAXON0uGGilpsAZpMx5bN1ZVavUlJETHif7w4HQ= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yassinebenaid/godump v0.11.1 h1:SPujx/XaYqGDfmNh7JI3dOyCUVrG0bG2duhO3Eh2EhI= +github.com/yassinebenaid/godump v0.11.1/go.mod h1:dc/0w8wmg6kVIvNGAzbKH1Oa54dXQx8SNKh4dPRyW44= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +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/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/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/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.4.2/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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +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/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20190328211700-ab21143f2384/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-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-20190828213141-aed303cbaa74/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-20191112195655-aa38f8e97acc/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.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +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-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/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/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/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= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +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.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/ingredient/ingredient.go b/ingredient/ingredient.go new file mode 100644 index 0000000..87863be --- /dev/null +++ b/ingredient/ingredient.go @@ -0,0 +1,152 @@ +package ingredient + +import ( + "autobrew/utils" + "fmt" +) + +type IngredientType int + +const ( + Base IngredientType = iota + TopShelf + BottomShelf +) + +type Ingredient struct { + string + IngredientType +} + +var ( + // Base + Water Ingredient = Ingredient{"Water", Base} + Wine Ingredient = Ingredient{"Wine", Base} + Oil Ingredient = Ingredient{"Oil", Base} + Spirits Ingredient = Ingredient{"Spirits", Base} + + // Top Shelf + Belladonna Ingredient = Ingredient{"Belladonna", TopShelf} + Chamomile Ingredient = Ingredient{"Chamomile", TopShelf} + Comfrey Ingredient = Ingredient{"Comfrey", TopShelf} + Dandelion Ingredient = Ingredient{"Dandelion", TopShelf} + ElderberryLeaves Ingredient = Ingredient{"Elderberry Leaves", TopShelf} + Eyebright Ingredient = Ingredient{"Eyebright", TopShelf} + Feverfew Ingredient = Ingredient{"Feverfew", TopShelf} + Henbane Ingredient = Ingredient{"Henbane", TopShelf} + HerbParis Ingredient = Ingredient{"Herb Paris", TopShelf} + Marigold Ingredient = Ingredient{"Marigold", TopShelf} + Mint Ingredient = Ingredient{"Mint", TopShelf} + Nettle Ingredient = Ingredient{"Nettle", TopShelf} + Poppy Ingredient = Ingredient{"Poppy", TopShelf} + Sage Ingredient = Ingredient{"Sage", TopShelf} + StJohnsWort Ingredient = Ingredient{"St. John's Wort", TopShelf} + Thistle Ingredient = Ingredient{"Thistle", TopShelf} + Valerian Ingredient = Ingredient{"Valerian", TopShelf} + Wormwood Ingredient = Ingredient{"Wormwood", TopShelf} + + // Bottom Shelf + AmanitaMuscaria Ingredient = Ingredient{"Amanita Muscaria", BottomShelf} + BoarsTusk Ingredient = Ingredient{"Boar's Tusk", BottomShelf} + Cobweb Ingredient = Ingredient{"Cobweb", BottomShelf} + Charcoal Ingredient = Ingredient{"Charcoal", BottomShelf} + Ginger Ingredient = Ingredient{"Ginger", BottomShelf} + LeachedCoal Ingredient = Ingredient{"Leached Coal", BottomShelf} + Saltpetre Ingredient = Ingredient{"Saltpetre", BottomShelf} + Sulphur Ingredient = Ingredient{"Sulphur", BottomShelf} +) + +func IngredientFromString(s string) (Ingredient, error) { + s = utils.Strip(s) + + switch s { + case "Water": + return Water, nil + case "Wine": + return Wine, nil + case "Oil": + return Oil, nil + case "Spirits": + return Spirits, nil + case "Belladonna": + return Belladonna, nil + case "Chamomile": + return Chamomile, nil + case "Comfrey": + return Comfrey, nil + case "Dandelion": + return Dandelion, nil + case "ElderberryLeaves": + return ElderberryLeaves, nil + case "Eyebright": + return Eyebright, nil + case "Feverfew": + return Feverfew, nil + case "Henbane": + return Henbane, nil + case "HerbParis": + return HerbParis, nil + case "Marigold": + return Marigold, nil + case "Mint": + return Mint, nil + case "Nettle": + return Nettle, nil + case "Poppy": + return Poppy, nil + case "Sage": + return Sage, nil + case "StJohnsWort": + return StJohnsWort, nil + case "Thistle": + return Thistle, nil + case "Valerian": + return Valerian, nil + case "Wormwood": + return Wormwood, nil + case "AmanitaMuscaria": + return AmanitaMuscaria, nil + case "BoarsTusk": + return BoarsTusk, nil + case "Cobweb": + return Cobweb, nil + case "Charcoal": + return Charcoal, nil + case "Ginger": + return Ginger, nil + case "LeachedCoal": + return LeachedCoal, nil + case "Saltpetre": + return Saltpetre, nil + case "Sulphur": + return Sulphur, nil + default: + return Water, fmt.Errorf("Invalid Ingredient: %s", s) + } +} + +func (a *Ingredient) UnmarshalYAML(unmarshal func(any) error) error { + var s string + err := unmarshal(&s) + if err != nil { + return err + } + + b, err := IngredientFromString(s) + + *a = b + + return nil +} + +func (i Ingredient) Name() string { + return i.string +} + +func (i Ingredient) IsBase() bool { + return i.IngredientType == Base +} + +func (i Ingredient) BottomShelf() bool { + return i.IngredientType == BottomShelf +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..6b46f77 --- /dev/null +++ b/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "autobrew/alchemy" + "autobrew/parser" + "autobrew/recipe" + "autobrew/utils" + "embed" + "fmt" + "image" + "os" + "slices" + "strings" + + config "github.com/ThomasObenaus/go-conf" + + "github.com/bendahl/uinput" +) + +//go:embed recipes.txt +var s embed.FS + +type Config struct { + X int `cfg:"{'name':'x','desc':'x offset','default':0}"` + Y int `cfg:"{'name':'y','desc':'y offset','default':0}"` + Width int `cfg:"{'name':'width','desc':'search area width','default':2560}"` + Height int `cfg:"{'name':'height','desc':'search area height','default':1440}"` + Save bool `cfg:"{'name':'save','desc':'save ocr image as output.png','default':false}"` + List bool `cfg:"{'name':'list','desc':'list all recipes','default':false,'short':'l'}"` + Recipe string `cfg:"{'name':'recipe','desc':'specific recipe to make','short':'r','default':''}"` + Amount int `cfg:"{'name':'amount','desc':'amount to make','default':1,'short':'n'}"` +} + +var cfg = Config{ + X: 2560, + Y: 0, + Width: 1280, + Height: 1440, + Save: false, + List: false, + Amount: 1, +} + +func main() { + + prefixForEnvironmentVariables := "AUTOBREW" + nameOfTheConfig := "AUTOBREW" + provider, err := config.NewConfigProvider( + &cfg, + nameOfTheConfig, + prefixForEnvironmentVariables, + ) + if err != nil { + panic(err) + } + + if len(os.Args) >= 1 { + err = provider.ReadConfig(os.Args[1:]) + if err != nil { + fmt.Printf("Error: %s\n", err.Error()) + fmt.Println("Usage:") + fmt.Print(provider.Usage()) + os.Exit(1) + } + } + + recipes_src, err := s.Open("recipes.txt") + utils.Check(err) + defer recipes_src.Close() + + var book map[string][]recipe.Step = parser.FromText(recipes_src) + + if cfg.List { + list := make([]string, 0) + for v := range book { + list = append(list, v) + } + slices.Sort(list) + fmt.Println(strings.Join(list, "\n")) + os.Exit(0) + } else { + if len(cfg.Recipe) == 0 { + fmt.Println("Recipe is required") + fmt.Print(provider.Usage()) + os.Exit(1) + } + } + + if _, ok := book[cfg.Recipe]; !ok { + panic(fmt.Errorf("Recipe %s not found\n", cfg.Recipe)) + } + + mouse, err := uinput.CreateMouse("/dev/uinput", []byte("brewer-mouse")) + utils.Check(err) + defer mouse.Close() + + keyboard, err := uinput.CreateKeyboard("/dev/uinput", []byte("brewer-keyboard")) + utils.Check(err) + defer keyboard.Close() + + alchemy := alchemy.NewAlchemy(mouse, keyboard, image.Rect(cfg.X, cfg.Y, cfg.Width+cfg.X, cfg.Height+cfg.Y), cfg.Save) + + alchemy.Brew(book, cfg.Recipe, cfg.Amount) +} diff --git a/parser/parser.go b/parser/parser.go new file mode 100644 index 0000000..96cf4ee --- /dev/null +++ b/parser/parser.go @@ -0,0 +1,186 @@ +package parser + +import ( + "autobrew/action" + "autobrew/ingredient" + "autobrew/recipe" + "autobrew/utils" + "bufio" + "io/fs" + "log" + "regexp" + "strconv" + "strings" +) + +var add_to_cauldron = regexp.MustCompile(`(?mi)^Add (\d+ .+)+? to the cauldron\.$`) +var add_to_mortar = regexp.MustCompile(`(?mi)^Add (\d+ .+)+? to the mortar, grind, and pour into the cauldron\.$`) +var boil = regexp.MustCompile(`(?mi)^Boil for (\d+) turn[s]*\.$`) +var boil_bellows = regexp.MustCompile(`(?mi)^Boil for (\d+) turn[s]* using bellows\.$`) +var extract_amount = regexp.MustCompile(`(?mi)^(\d+) (.*)$`) + +func transformStep(lines []string) (name string, steps []recipe.Step, err error) { + + name, lines = lines[0], lines[1:] + + var ingredients []ingredient.Ingredient + + for index, line := range lines { + if strings.HasPrefix(line, "Liquid Base:") { + i, err := ingredient.IngredientFromString(strings.TrimSpace(strings.Split(line, ": ")[1])) + utils.Check(err) + + ingredients = append(ingredients, i) + } + + if strings.HasPrefix(line, "Ingredients:") { + parts := strings.TrimSpace(strings.Split(line, ":")[1]) + for _, i := range strings.Split(parts, ",") { + + a := strings.Split(strings.TrimSpace(i), " x") + i, err := ingredient.IngredientFromString(strings.TrimSpace(a[0])) + utils.Check(err) + + amount, err := strconv.Atoi(a[1]) + utils.Check(err) + + for range amount { + ingredients = append(ingredients, i) + } + } + } + + if strings.HasPrefix(line, "Recipe") { + lines = lines[index+1:] + } + } + + steps = append(steps, recipe.Step{ + Action: action.Prepare, + Ingredients: ingredients, + }) + + for _, line := range lines { + line = strings.TrimSpace(line) + if match := add_to_cauldron.FindStringSubmatch(line); match != nil { + parts := strings.Split(match[1], " and ") + var ingredients []ingredient.Ingredient + for _, part := range parts { + if match := extract_amount.FindStringSubmatch(part); match != nil { + i, err := strconv.Atoi(match[1]) + utils.Check(err) + + for range i { + i, err := ingredient.IngredientFromString(strings.TrimSpace(match[2])) + utils.Check(err) + + ingredients = append(ingredients, i) + } + + } + } + steps = append(steps, recipe.Step{ + Action: action.AddToCauldron, + Ingredients: ingredients, + }) + } + + if match := add_to_mortar.FindStringSubmatch(line); match != nil { + parts := strings.Split(match[1], " and ") + var ingredients []ingredient.Ingredient + for _, part := range parts { + if match := extract_amount.FindStringSubmatch(part); match != nil { + i, err := strconv.Atoi(match[1]) + utils.Check(err) + + for range i { + i, err := ingredient.IngredientFromString(strings.TrimSpace(match[2])) + utils.Check(err) + + ingredients = append(ingredients, i) + } + + } + } + steps = append(steps, recipe.Step{ + Action: action.GrindIngredients, + Ingredients: ingredients, + }) + } + + if match := boil.FindStringSubmatch(line); match != nil { + i, err := strconv.Atoi(match[1]) + utils.Check(err) + steps = append(steps, recipe.Step{ + Action: action.Boil, + Turns: i, + }) + } + + if match := boil_bellows.FindStringSubmatch(line); match != nil { + i, err := strconv.Atoi(match[1]) + utils.Check(err) + steps = append(steps, recipe.Step{ + Action: action.BoilBellows, + Turns: i, + }) + } + + if strings.HasPrefix(line, "Pour into phial.") { + steps = append(steps, recipe.Step{ + Action: action.PourPhial, + }) + } + + if strings.HasPrefix(line, "Prepare phial and distill.") { + steps = append(steps, recipe.Step{ + Action: action.Distill, + }) + } + + if strings.HasPrefix(line, "Pour into a mortar and grind.") { + steps = append(steps, recipe.Step{ + Action: action.GrindPotion, + }) + } + } + + return +} + +func FromText(file fs.File) map[string][]recipe.Step { + scanner := bufio.NewScanner(file) + // optionally, resize scanner's capacity for lines over 64K, see next example + recipes := make(map[string][]recipe.Step, 0) + lines := make([]string, 0) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if len(line) == 0 { + continue + } + + if line == "--" { + name, recipe, err := transformStep(lines) + utils.Check(err) + recipes[name] = recipe + lines = make([]string, 0) + continue + } + + lines = append(lines, line) + + } + + if len(lines) > 0 { + name, recipe, err := transformStep(lines) + utils.Check(err) + recipes[name] = recipe + } + + if err := scanner.Err(); err != nil { + log.Fatal(err) + } + + return recipes +} diff --git a/recipe/recipe.go b/recipe/recipe.go new file mode 100644 index 0000000..1b93ae7 --- /dev/null +++ b/recipe/recipe.go @@ -0,0 +1,24 @@ +package recipe + +import ( + "autobrew/action" + "autobrew/ingredient" +) + +type Recipe struct { + Steps []Step + Ingredients []ingredient.Ingredient +} + +func (r *Recipe) LoadIngredients() { + for _, step := range r.Steps { + if step.Action == action.Prepare { + for _, i := range step.Ingredients { + if i.IngredientType != ingredient.Base { + r.Ingredients = append(r.Ingredients, i) + } + } + break + } + } +} diff --git a/recipe/step.go b/recipe/step.go new file mode 100644 index 0000000..042eac7 --- /dev/null +++ b/recipe/step.go @@ -0,0 +1,31 @@ +package recipe + +import ( + "autobrew/action" + "autobrew/ingredient" +) + +type Step struct { + Ingredients []ingredient.Ingredient + Action action.StepAction + Turns int +} + +func (f *Step) UnmarshalYAML(unmarshal func(any) error) error { + var t struct { + Ingredients []ingredient.Ingredient `yaml:"ingredients"` + Action action.StepAction `yaml:"action"` + Turns int `yaml:"turns"` + } + + err := unmarshal(&t) + if err != nil { + return err + } + + f.Action = t.Action + f.Ingredients = t.Ingredients + f.Turns = t.Turns + + return nil +} diff --git a/recipes.txt b/recipes.txt new file mode 100644 index 0000000..2e1d9a7 --- /dev/null +++ b/recipes.txt @@ -0,0 +1,433 @@ +Aesop + +Liquid Base: Spirits + +Ingredients: Comfrey x2, Boar's Tusk x1, Belladonna x1 + +Recipe: + + Add 2 Comfrey to the mortar, grind, and pour into the cauldron. + Add 1 Boar's Tusk to the cauldron. + Boil for 2 turns. + Add 1 Belladonna to the mortar, grind, and pour into the cauldron. + Prepare phial and distill. + +-- + +Aqua Vitalis + +Liquid Base: Water + +Ingredients: Dandelion x2, Marigold x1 + +Recipe: + + Add 2 Dandelion to the cauldron. + Boil for 1 turn. + Add 1 Marigold to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Prepare phial and distill. + +-- + +Artemisia + +Liquid Base: Spirits + +Ingredients: Sage x1, Wormwood x2 + +Recipe: + + Add 1 Sage to the cauldron. + Boil for 1 turn. + Add 2 Wormwood to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Prepare phial and distill. + +-- + +Bane Poison + +Liquid Base: Wine + +Ingredients: Wormwood x1, Belladonna x2, Amanita Muscaria x1 + +Recipe: + + Add 1 Wormwood to the cauldron. + Boil for 2 turns. + Add 2 Belladonna to the mortar, grind, and pour into the cauldron. + Boil for 1 turn using bellows. + Add 1 Amanita Muscaria to the cauldron. + Prepare phial and distill. + +-- + +Bowman's Brew + +Liquid Base: Spirits + +Ingredients: Eyebright x2, St. John's Wort x1 + +Recipe: + + Add 2 Eyebright to the cauldron. + Boil for 3 turns. + Add 1 St. John's Wort to the mortar, grind, and pour into the cauldron. + Boil for 1 turn using bellows. + Prepare phial and distill. + +-- + +Buck's Blood + +Liquid Base: Oil + +Ingredients: St. John's Wort x1, Comfrey x1, Dandelion x1 + +Recipe: + + Add 1 St. John's Wort to the mortar, grind, and pour into the cauldron. + Add 1 Comfrey to the cauldron. + Boil for 1 turn. + Add 1 Dandelion to the cauldron. + Boil for 1 turn using bellows. + Pour into phial. + +-- + +Chamomile Decoction + +Liquid Base: Wine + +Ingredients: Chamomile x2, Sage x1 + +Recipe: + + Add 2 Chamomile to the cauldron. + Boil for 1 turn. + Add 1 Sage to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Cockerel + +Liquid Base: Spirits + +Ingredients: Mint x2, Valerian x1 + +Recipe: + + Add 2 Mint to the mortar, grind, and pour into the cauldron. + Boil for 1 turn. + Add 1 Valerian to the cauldron. + Boil for 2 turns. + Prepare phial and distill. + +-- + +Digestive Potion + +Liquid Base: Water + +Ingredients: Thistle x2, Nettle x1, Charcoal x1 + +Recipe: + + Add 2 Thistle to the cauldron. + Boil for 2 turns. + Add 1 Nettle to the mortar, grind, and pour into the cauldron. + Boil for 1 turn. + Add 1 Charcoal to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Dollmaker Poison + +Liquid Base: Spirits + +Ingredients: Valerian x1, Herb Paris x2 + +Recipe: + + Add 2 Herb Paris to the cauldron. + Boil for 3 turns. + Add 1 Valerian to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Prepare phial and distill. + +-- + +Embrocation + +Liquid Base: Oil + +Ingredients: Eyebright x1, Poppy x1, Valerian x1, Boar's Tusk x1 + +Recipe: + + Add 1 Poppy and 1 Valerian to the cauldron. + Boil for 2 turns. + Add 1 Eyebright to the mortar, grind, and pour into the cauldron. + Add 1 Boar's Tusk to the cauldron. + Boil for 1 turn. + Pour into phial. + +-- + +Fever Tonic + +Liquid Base: Wine + +Ingredients: Feverfew x3, Ginger x2, Elderberry Leaves x1 + +Recipe: + + Add 3 Feverfew to the cauldron. + Boil for 2 turns. + Add 1 Elderberry Leaves to the mortar, grind, and pour into the cauldron. + Add 2 Ginger to the cauldron. + Prepare phial and distill. + +-- + +Fox + +Liquid Base: Oil + +Ingredients: Nettle x1, St. John's Wort x1, Belladonna x1, Charcoal x1 + +Recipe: + + Add 1 St. John's Wort and 1 Nettle to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Add 1 Charcoal to the mortar, grind, and pour into the cauldron. + Add 1 Belladonna to the cauldron. + Boil for 1 turn. + Pour into phial. + +-- + +Hair o' the Dog + +Liquid Base: Water + +Ingredients: Sage x1, St. John's Wort x1, Mint x1 + +Recipe: + + Add 1 Sage and 1 St. John's Wort to the cauldron. + Boil for 3 turns. + Add 1 Mint to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Lead Shot Gunpowder + +Liquid Base: Water + +Ingredients: Sulphur x1, Saltpetre x1, Charcoal x1 + +Recipe: + + Add 1 Sulphur and 1 Saltpetre to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Add 1 Charcoal to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Pour into a mortar and grind. + +-- + +Lethean Water + +Liquid Base: Spirits + +Ingredients: Wormwood x2, Belladonna x1, Henbane x1 + +Recipe: + + Add 2 Wormwood to the mortar, grind, and pour into the cauldron. + Add 1 Belladonna to the cauldron. + Boil for 3 turns. + Add 1 Henbane to the cauldron. + Prepare phial and distill. + +-- + +Lion Perfume + +Liquid Base: Spirits + +Ingredients: Sage x2, Mint x2 + +Recipe: + + Add 2 Sage to the cauldron. + Boil for 2 turns. + Add 2 Mint to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Lullaby + +Liquid Base: Oil + +Ingredients: Poppy x1, Herb Paris x1, Thistle x1 + +Recipe: + + Add 1 Poppy to the cauldron. + Boil for 1 turn. + Add 1 Thistle to the cauldron. + Boil for 1 turn. + Add 1 Herb Paris to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Marigold Decoction + +Liquid Base: Water + +Ingredients: Nettle x1, Marigold x2 + +Recipe: + + Add 1 Nettle to the cauldron. + Boil for 2 turns. + Add 2 Marigold to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Mintha Perfume + +Liquid Base: Wine + +Ingredients: Dandelion x3, Marigold x1, Mint x1 + +Recipe: + + Add 3 Dandelion and 1 Mint to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Add 1 Marigold to the cauldron. + Boil for 1 turn using bellows. + Prepare phial and distill. + +-- + +Moonshine + +Liquid Base: Spirits + +Ingredients: Mint x2, Wormwood x2 + +Recipe: + + Add 2 Wormwood to the cauldron. + Boil for 2 turns. + Add 2 Mint to the mortar, grind, and pour into the cauldron. + Boil for 1 turn. + Prepare phial and distill. + +-- + +Nighthawk + +Liquid Base: Water + +Ingredients: Eyebright x2, Belladonna x1, Chamomile x1 + +Recipe: + + Add 2 Eyebright to the mortar, grind, and pour into the cauldron. + Add 1 Belladonna to the cauldron. + Boil for 3 turns. + Add 1 Chamomile to the mortar, grind, and pour into the cauldron. + Pour into phial. + +-- + +Painkiller Brew + +Liquid Base: Spirits + +Ingredients: Poppy x3, Comfrey x1, Marigold x1 + +Recipe: + + Add 3 Poppy to the mortar, grind, and pour into the cauldron. + Add 1 Marigold to the cauldron. + Boil for 1 turn using bellows. + Add 1 Comfrey to the cauldron + Boil for 2 turns. + Prepare phial and distill. + +-- + +Quickfinger + +Liquid Base: Water + +Ingredients: Eyebright x2, Valerian x2, Cobweb x1 + +Recipe: + + Add 1 Cobweb and 2 Eyebright to the cauldron. + Boil for 3 turns. + Add 2 Valerian to the mortar, grind, and pour into the cauldron. + Boil for 1 turn. + Pour into phial. + +-- + +Saviour Schnapps + +Liquid Base: Wine + +Ingredients: Nettle x1, Belladonna x2 + +Recipe: + + Add 1 Nettle to the cauldron. + Boil for 2 turns. + Add 2 Belladonna to the mortar, grind, and pour into the cauldron. + Boil for 1 turn. + Pour into phial. + +-- + +Scattershot Powder + +Liquid Base: Water + +Ingredients: Sulphur x1, Saltpetre x1, Leached Coal x1 + +Recipe: + + Add 1 Sulphur and 1 Saltpetre to the mortar, grind, and pour into the cauldron. + Boil for 3 turns. + Add 1 Leached Coal to the mortar, grind, and pour into the cauldron. + Boil for 1 turn. + Pour into a mortar and grind. + +-- + +Soap + +Liquid Base: Oil + +Ingredients: Thistle x2, Dandelion x1, Charcoal x1 + +Recipe: + + Add 2 Thistle to the mortar, grind, and pour into the cauldron. + Boil for 2 turns. + Add 1 Dandelion to the cauldron. + Boil for 1 turn. + Add 1 Charcoal to the mortar, grind, and pour into the cauldron. + Pour into phial. diff --git a/utils/strings.go b/utils/strings.go new file mode 100644 index 0000000..156358a --- /dev/null +++ b/utils/strings.go @@ -0,0 +1,45 @@ +package utils + +import "strings" + +func Strip(s string) string { + var result strings.Builder + for i := 0; i < len(s); i++ { + b := s[i] + if ('a' <= b && b <= 'z') || + ('A' <= b && b <= 'Z') || + ('0' <= b && b <= '9') { + result.WriteByte(b) + } + } + return result.String() +} + +func DedupeSlice[T comparable](sliceList []T) []T { + dedupeMap := make(map[T]struct{}) + list := []T{} + + for _, slice := range sliceList { + if _, exists := dedupeMap[slice]; !exists { + dedupeMap[slice] = struct{}{} + list = append(list, slice) + } + } + + return list +} + +func Check(e error) { + if e != nil { + panic(e) + } +} + +func Filter[T any](ss []T, test func(T) bool) (ret []T) { + for _, s := range ss { + if test(s) { + ret = append(ret, s) + } + } + return +}