diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 38c978b..0000000 --- a/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -* -!*.example.* -!**/go.mod -!**/go.sum -!**/*.go diff --git a/.editorconfig b/.editorconfig index 75f84cb..84dd35a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,11 +10,7 @@ trim_trailing_whitespace = true indent_style = tab indent_size = 4 -[{Dockerfile,Dockerfile.*}] -indent_style = space -indent_size = 4 - -[*.{yml,yaml,hcl,toml,json}] +[*.{yml,yaml,hcl,toml}] indent_style = space indent_size = 2 diff --git a/Dockerfile.multiarch b/Dockerfile.multiarch index 1dfb97d..a34e326 100644 --- a/Dockerfile.multiarch +++ b/Dockerfile.multiarch @@ -10,8 +10,7 @@ RUN apt-get update && \ "arm64") apt-get install -y gcc-aarch64-linux-gnu ;; \ "armv7") apt-get install -y gcc-arm-linux-gnueabihf ;; \ "riscv64") apt-get install -y gcc-riscv64-linux-gnu ;; \ - "amd64") apt-get install -y gcc ;; \ - *) echo "Arch not supported" && exit 1 ;; \ + *) apt-get install -y gcc ;; \ esac WORKDIR /tmp/app @@ -21,8 +20,7 @@ RUN go mod download && \ "arm64") export CC="aarch64-linux-gnu-gcc" PIE=true ;; \ "armv7") export CC="arm-linux-gnueabihf-gcc" GOARM="7" ;; \ "riscv64") export CC="riscv64-linux-gnu-gcc" ;; \ - "amd64") export CC="gcc" PIE=true ;; \ - *) echo "Arch not supported" && exit 1 ;; \ + *) export CC="gcc" PIE=true ;; \ esac && \ export CGO_ENABLED=1 && \ if [ $PIE = true ]; then \ diff --git a/cmd/database.go b/cmd/database.go index 44fff72..d90188d 100644 --- a/cmd/database.go +++ b/cmd/database.go @@ -57,7 +57,7 @@ func NewDatabase(driver string, connectString string) (*Database, error) { func (db *Database) GetNewestTweet(channel string) (*Tweet, error) { tweet := Tweet{} - err := db.Get(&tweet, "SELECT * FROM tweet WHERE channel=$1 ORDER BY timestamp DESC, snowflake DESC LIMIT 1", channel) + err := db.Get(&tweet, "SELECT * FROM tweet WHERE channel=$1 ORDER BY timestamp DESC LIMIT 1", channel) if err != nil { return nil, err } @@ -66,7 +66,7 @@ func (db *Database) GetNewestTweet(channel string) (*Tweet, error) { func (db *Database) GetTweets(channel string) ([]*Tweet, error) { tweet := []*Tweet{} - err := db.Select(&tweet, "SELECT * FROM tweet WHERE channel=$1 ORDER BY timestamp DESC, snowflake DESC", channel) + err := db.Select(&tweet, "SELECT * FROM tweet WHERE channel=$1 ORDER BY timestamp DESC", channel) if err != nil { return nil, err } @@ -80,7 +80,7 @@ func (db *Database) ContainsTweet(channel string, tweet *ts.Tweet) (bool, error) } t := Tweet{} - rows, err := db.Queryx("SELECT * FROM tweet WHERE channel=$1 ORDER BY timestamp DESC, snowflake DESC", channel) + rows, err := db.Queryx("SELECT * FROM tweet WHERE channel=$1 ORDER BY timestamp DESC", channel) if err != nil { return false, err } @@ -124,7 +124,7 @@ func (db *Database) PruneOldestTweets(channel string) error { tx.Rollback() return err } - rows, err := tx.Queryx("SELECT tweet_id from tweet WHERE channel=$1 ORDER by timestamp ASC, snowflake ASC LIMIT $2", channel, count-KeepTweets) + rows, err := tx.Queryx("SELECT tweet_id from tweet WHERE channel=$1 ORDER by timestamp ASC LIMIT $2", channel, count-KeepTweets) if err != nil { tx.Rollback() return err diff --git a/cmd/embed.go b/cmd/embed.go index 7740004..b06d0d2 100644 --- a/cmd/embed.go +++ b/cmd/embed.go @@ -4,21 +4,18 @@ import ( "errors" "strconv" "strings" - "time" ) type Embed struct { - Author Author `json:"author,omitempty"` - Title string `json:"title,omitempty"` - URL string `json:"url,omitempty"` - Description string `json:"description,omitempty"` - Timestamp string `json:"timestamp,omitempty"` - Color int64 `json:"color,omitempty"` - Fields []Field `json:"fields,omitempty"` + Author Author `json:"author"` + Title string `json:"title"` + URL string `json:"url"` + Description string `json:"description"` + Color int64 `json:"color"` + Fields []Field `json:"fields"` Thumbnail Image `json:"thumbnail,omitempty"` Image Image `json:"image,omitempty"` - Video Video `json:"video,omitempty"` - Footer Footer `json:"footer,omitempty"` + Footer Footer `json:"footer"` } type Author struct { @@ -42,75 +39,60 @@ type Image struct { URL string `json:"url"` } -type Video struct { - URL string `json:"url"` -} - -func (w *Webhook) NewEmbedWithURL(URL string) *Embed { - emb := Embed{URL: URL} +func (w *Webhook) NewEmbed(Title, Description, URL string) { + emb := Embed{Title: Title, Description: Description, URL: URL} w.Embeds = append(w.Embeds, &emb) - return &emb } -func (w *Webhook) NewEmbed() *Embed { - emb := Embed{} - w.Embeds = append(w.Embeds, &emb) - return &emb +func (w *Webhook) SetAuthor(Name, URL, IconURL string) { + if len(w.Embeds) == 0 { + emb := Embed{Author: Author{Name, URL, IconURL}} + w.Embeds = append(w.Embeds, &emb) + } else { + w.Embeds[0].Author = Author{Name, URL, IconURL} + } } -func (e *Embed) SetTitle(Title string) *Embed { - e.Title = Title - return e -} - -func (e *Embed) SetText(Description string) *Embed { - e.Description = Description - return e -} - -func (e *Embed) SetAuthor(Name, URL, IconURL string) *Embed { - e.Author = Author{Name, URL, IconURL} - return e -} - -func (e *Embed) SetColor(color string) (*Embed, error) { +func (w *Webhook) SetColor(color string) error { color = strings.Replace(color, "0x", "", -1) color = strings.Replace(color, "0X", "", -1) color = strings.Replace(color, "#", "", -1) colorInt, err := strconv.ParseInt(color, 16, 64) if err != nil { - return nil, errors.New("Invalid hex code passed") + return errors.New("Invalid hex code passed") } - e.Color = colorInt - return e, nil + w.Embeds[0].Color = colorInt + return nil } -func (e *Embed) SetThumbnail(URL string) *Embed { - e.Thumbnail = Image{URL} - return e +func (w *Webhook) SetThumbnail(URL string) error { + if len(w.Embeds) < 1 { + return errors.New("Invalid Embed passed in, Webhook.Embeds must have at least one Embed element") + } + w.Embeds[0].Thumbnail = Image{URL} + return nil } -func (e *Embed) SetImage(URL string) *Embed { - e.Image = Image{URL} - return e +func (w *Webhook) SetImage(URL string) error { + if len(w.Embeds) < 1 { + return errors.New("Invalid Embed passed in, Webhook.Embeds must have at least one Embed element") + } + w.Embeds[0].Image = Image{URL} + return nil } -func (e *Embed) SetVideo(URL string) *Embed { - e.Video = Video{URL} - return e +func (w *Webhook) SetFooter(Text, IconURL string) error { + if len(w.Embeds) < 1 { + return errors.New("Invalid Embed passed in, Webhook.Embeds must have at least one Embed element") + } + w.Embeds[0].Footer = Footer{Text, IconURL} + return nil } -func (e *Embed) SetFooter(Text, IconURL string) *Embed { - e.Footer = Footer{Text, IconURL} - return e -} - -func (e *Embed) SetTimestamp(timestamp time.Time) *Embed { - e.Timestamp = timestamp.Format(time.RFC3339) - return e -} - -func (e *Embed) AddField(Name, Value string, Inline bool) *Embed { - e.Fields = append(e.Fields, Field{Name, Value, Inline}) - return e +func (w *Webhook) AddField(Name, Value string, Inline bool) error { + if len(w.Embeds) < 1 { + return errors.New("Invalid Embed passed in, Webhook.Embeds must have at least one Embed element") + } + w.Embeds[0].Fields = append(w.Embeds[0].Fields, Field{Name, Value, Inline}) + return nil } diff --git a/cmd/tweeter.go b/cmd/tweeter.go index e9d03b5..1bbd295 100644 --- a/cmd/tweeter.go +++ b/cmd/tweeter.go @@ -100,33 +100,26 @@ func Run() { log.Println("We're already logged in, skipping login...") } else { scraper.ClearCookies() - if len(config.Username) > 0 { - err = scraper.Login(config.Username, config.Password) - if err != nil { - log.Fatalf("An error occurred during scraper login: %s\n", err.Error()) - } else { - log.Printf("New Login - Saving cookies to %s\n", cookiePath) - js, jsonErr := json.Marshal(scraper.GetCookies()) - if jsonErr != nil { - log.Fatalf("An error occurred during cookie serialization: %s\n", jsonErr.Error()) - } - f, fErr := os.Create(cookiePath) - if fErr != nil { - log.Fatalf("Failed to create cookie file at %s with the following error: %s\n", cookiePath, fErr.Error()) - } - f.Write(js) - writeErr := f.Close() - if writeErr != nil { - log.Fatalf("An error occurred on closing cookie file: %s\n", writeErr.Error()) - } - } - } else { + err = scraper.Login(config.Username, config.Password) + if err != nil { + log.Printf("An error occurred during scraper login: %s\n", err.Error()) log.Println("Trying open account login... ") err = scraper.LoginOpenAccount() if err != nil { log.Fatalf("An error occurred during scraper login: %s\n", err.Error()) } defer scraper.Logout() + } else { + log.Printf("New Login - Saving cookies to %s\n", cookiePath) + js, jsonErr := json.Marshal(scraper.GetCookies()) + if jsonErr != nil { + log.Fatalf("An error occurred during cookie serialization: %s\n", jsonErr.Error()) + } + f, fErr := os.Create(cookiePath) + if fErr != nil { + log.Fatalf("Failed to create cookie file at %s with the following error: %s\n", cookiePath, fErr.Error()) + } + f.Write(js) } } diff --git a/cmd/webhook.go b/cmd/webhook.go index deb9176..9969eab 100644 --- a/cmd/webhook.go +++ b/cmd/webhook.go @@ -6,13 +6,6 @@ import ( ts "github.com/n0madic/twitter-scraper" "log" "net/http" - "strings" - //"strconv" -) - -const ( - BaseURL = "https://twitter.com/" - BaseIcon = "https://abs.twimg.com/icons/apple-touch-icon-192x192.png" ) type Webhook struct { @@ -32,74 +25,24 @@ type Mention struct { func sendToWebhook(webhookURL string, tweets []*ts.Tweet) { for _, tweet := range tweets { + // Temporarily hardcoded data := Webhook{ - Content: "<" + tweet.PermanentURL + ">", + Content: tweet.PermanentURL, Mention: &Mention{Parse: []string{"roles"}}, + Embeds: []*Embed{}, } - urlsToAppend := []string{} - userUrl := BaseURL + tweet.Username - - mainEmbed := data.NewEmbedWithURL(tweet.PermanentURL) - mainEmbed.SetAuthor(tweet.Name+" (@"+tweet.Username+")", userUrl, "https://unavatar.io/twitter/"+tweet.Username) - mainEmbed.SetText(tweet.Text) - mainEmbed.SetColor("#26a7de") - mainEmbed.SetFooter("Twitter", BaseIcon) - mainEmbed.SetTimestamp(tweet.TimeParsed) - //mainEmbed.SetFooter("Twitter • ", BaseIcon) - - for i, photo := range tweet.Photos { - embed := mainEmbed - if i > 0 { - embed = data.NewEmbedWithURL(tweet.PermanentURL) - } - embed.SetImage(photo.URL) - } - for i, gif := range tweet.GIFs { - embed := mainEmbed - if i > 0 { - embed = data.NewEmbedWithURL(tweet.PermanentURL) - } - embed.SetImage(gif.Preview) - } - for i, video := range tweet.Videos { - embed := mainEmbed - if i > 0 { - embed = data.NewEmbedWithURL(tweet.PermanentURL) - } - // Video embeds are not supported right now - embed.SetImage(video.Preview) - embed.SetVideo(video.URL) // This has sadly no effect - urlsToAppend = append(urlsToAppend, strings.Replace(tweet.PermanentURL, "twitter", "fxtwitter", 1)) - } - - err := sendRequest(webhookURL, &data) + jsonData, err := json.Marshal(data) if err != nil { - log.Println("Error while sending webhook for tweet %s: %s", tweet.ID, err.Error()) + log.Printf("Error while generating JSON for tweet %s: %s", tweet.ID, err.Error()) continue } - for _, url := range urlsToAppend { - err := sendRequest(webhookURL, &Webhook{Content: url}) - if err != nil { - log.Println("Error while sending webhook for tweet %s: %s", tweet.ID, err.Error()) - continue - } + resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData)) + if err != nil { + log.Printf("Error while sending webhook for tweet %s: %s", tweet.ID, err.Error()) + continue } + defer resp.Body.Close() } } - -func sendRequest(url string, data *Webhook) error { - jsonData, err := json.Marshal(data) - if err != nil { - return err - } - - resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) - defer resp.Body.Close() - if err != nil { - return err - } - - return nil -} diff --git a/config.example.toml b/config.example.toml index c23a723..bf3549b 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1,4 +1,4 @@ -username = "" +username = "Someuser" password = "asd123" #proxyaddr = "socks5://localhost:5555" channels = [ diff --git a/docker-bake.hcl b/docker-bake.hcl index 2c6c7cc..49fef6f 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -26,6 +26,7 @@ target "prod" { #dockerfile = "Dockerfile" dockerfile = "Dockerfile.multiarch" output = ["type=registry"] + } target "dev" { inherits = ["default"] diff --git a/go.mod b/go.mod index 3c91360..ef4193e 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/BurntSushi/toml v1.3.2 github.com/jmoiron/sqlx v1.3.5 github.com/mattn/go-sqlite3 v1.14.17 - github.com/n0madic/twitter-scraper v0.0.0-20231012104931-652b5b5115a4 + github.com/n0madic/twitter-scraper v0.0.0-20230711213008-94503a2bc36c ) -require golang.org/x/net v0.17.0 // indirect +require golang.org/x/net v0.14.0 // indirect diff --git a/go.sum b/go.sum index 8fd4225..0110c05 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= @@ -11,21 +17,24 @@ github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/n0madic/twitter-scraper v0.0.0-20231012104931-652b5b5115a4 h1:U+UOzbyHe3Y5I/rSCUPsBKfVDq5YaMHWyLpljFU+B3I= -github.com/n0madic/twitter-scraper v0.0.0-20231012104931-652b5b5115a4/go.mod h1:qoLNLwgpaGspT8E82iwzof9xGsQTg35j36PlXrD3R4o= +github.com/n0madic/twitter-scraper v0.0.0-20230711213008-94503a2bc36c h1:pcEcP5jkXQuze3pkUDc7qtDZca1TP208LgkgXzVn/lc= +github.com/n0madic/twitter-scraper v0.0.0-20230711213008-94503a2bc36c/go.mod h1:VS1i48kYWIoiS0vz6k7GU6iuwcr6deD3CovldOR2rQE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -36,20 +45,26 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/text v0.3.0/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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=