├── static └── ghtracker-logo.png ├── main.go ├── pkg ├── config │ └── config.go ├── utils │ ├── textwrap │ │ ├── textwrap.go │ │ └── textwrap_test.go │ └── url │ │ ├── url.go │ │ └── url_test.go ├── runner │ ├── banners.go │ ├── outputer.go │ ├── engine_test.go │ ├── runner.go │ └── engine.go └── options │ └── options.go ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── issue-report.md └── workflows │ └── release-binary.yml ├── .gitignore ├── LICENSE ├── cmd └── root.go ├── go.mod ├── README.md └── go.sum /static/ghtracker-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zer0yu/ghtracker/HEAD/static/ghtracker-logo.png -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/zer0yu/ghtracker/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | NextButtonSelector = "#dependents > div.paginate-container > div > a" 5 | ItemSelector = "#dependents > div.Box > div.flex-items-center" 6 | RepoSelector = "span > a.text-bold" 7 | StarsSelector = "div > span:nth-child(1)" 8 | GithubURL = "https://github.com" 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/utils/textwrap/textwrap.go: -------------------------------------------------------------------------------- 1 | package textwrap 2 | 3 | import "unicode/utf8" 4 | 5 | func Shorten(text string, maxLen int) string { 6 | lastSpaceIx := maxLen 7 | len := 0 8 | for i, r := range text { 9 | if utf8.RuneLen(r) > 1 { 10 | len += 2 11 | } else { 12 | len++ 13 | } 14 | if len > maxLen { 15 | return text[:lastSpaceIx] + "..." 16 | } 17 | if r == ' ' { 18 | lastSpaceIx = i 19 | } 20 | } 21 | return text 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[Issue] " 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **ghtracker version** 14 | Include the version of ghtracker you are using, `ghtracker -version` 15 | 16 | **Complete command you used to reproduce this** 17 | 18 | 19 | **Screenshots** 20 | Add screenshots of the error for a better context. 21 | -------------------------------------------------------------------------------- /pkg/utils/url/url.go: -------------------------------------------------------------------------------- 1 | package url 2 | 3 | import ( 4 | "net/url" 5 | "path" 6 | "strings" 7 | ) 8 | 9 | func GetRelativeURL(inputURL string) (string, error) { 10 | if !strings.HasPrefix(inputURL, "http://") && !strings.HasPrefix(inputURL, "https://") { 11 | inputURL = "https://" + inputURL 12 | } 13 | 14 | u, err := url.Parse(inputURL) 15 | if err != nil { 16 | return "", err 17 | } 18 | 19 | // 清理路径并删除开头的斜杠 20 | relativeURL := path.Clean(u.Path) 21 | relativeURL = strings.TrimPrefix(relativeURL, "/") 22 | 23 | return relativeURL, nil 24 | } 25 | -------------------------------------------------------------------------------- /pkg/utils/textwrap/textwrap_test.go: -------------------------------------------------------------------------------- 1 | package textwrap 2 | 3 | import "testing" 4 | 5 | func TestShorten(t *testing.T) { 6 | testCases := []struct { 7 | text string 8 | maxLen int 9 | expected string 10 | }{ 11 | {"Hello, world!", 10, "Hello,..."}, 12 | {"Hello, world!", 5, "Hello..."}, 13 | {"Hello", 10, "Hello"}, 14 | } 15 | 16 | for _, tc := range testCases { 17 | result := Shorten(tc.text, tc.maxLen) 18 | if result != tc.expected { 19 | t.Errorf("Shorten(%q, %d) = %q; want %q", tc.text, tc.maxLen, result, tc.expected) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | .idea/ 20 | ghtracker 21 | 22 | # Go workspace file 23 | go.work 24 | -------------------------------------------------------------------------------- /pkg/runner/banners.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import "github.com/projectdiscovery/gologger" 4 | 5 | const banner = ` 6 | __ __ __ 7 | ____ / /_ / /__________ ______/ /_____ _____ 8 | / __ \/ __ \/ __/ ___/ __ \/ ___/ //_/ _ \/ ___/ 9 | / /_/ / / / / /_/ / / /_/ / /__/ ,< / __/ / 10 | \__, /_/ /_/\__/_/ \__,_/\___/_/|_|\___/_/ 11 | /____/ 12 | ` 13 | 14 | // Name 15 | const ToolName = `ghtracker` 16 | 17 | // showBanner is used to show the banner to the user 18 | func showBanner() { 19 | gologger.Print().Msgf("%s\n", banner) 20 | gologger.Print().Msgf("\t\tVersion v1.0.0\n\n") 21 | } 22 | -------------------------------------------------------------------------------- /pkg/utils/url/url_test.go: -------------------------------------------------------------------------------- 1 | package url 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestGetRelativeURL(t *testing.T) { 8 | testCases := []struct { 9 | input string 10 | expected string 11 | }{ 12 | {"https://github.com/AFLplusplus/LibAFL", "AFLplusplus/LibAFL"}, 13 | {"https://github.com/AFLplusplus/LibAFL/", "AFLplusplus/LibAFL"}, 14 | {"github.com/AFLplusplus/LibAFL", "AFLplusplus/LibAFL"}, 15 | } 16 | 17 | for _, tc := range testCases { 18 | result, err := GetRelativeURL(tc.input) 19 | if err != nil { 20 | t.Errorf("GetRelativeURL(%q) returned error: %v", tc.input, err) 21 | } 22 | if result != tc.expected { 23 | t.Errorf("GetRelativeURL(%q) = %q; want %q", tc.input, result, tc.expected) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/release-binary.yml: -------------------------------------------------------------------------------- 1 | name: 🎉 Release Binary 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: "Check out code" 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: "Set up Go" 22 | uses: actions/setup-go@v4 23 | with: 24 | go-version: 1.21.x 25 | 26 | - name: "Create release on GitHub" 27 | uses: goreleaser/goreleaser-action@v4 28 | with: 29 | args: "release --rm-dist" 30 | version: latest 31 | workdir: . 32 | env: 33 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" -------------------------------------------------------------------------------- /pkg/options/options.go: -------------------------------------------------------------------------------- 1 | package options 2 | 3 | type GHTopDepOptions struct { 4 | Version bool // Version is a flag that prints the current version. 5 | URL string // URL is the URL to fetch repositories from. 6 | Repositories bool // Repositories is a flag that indicates if repositories should be fetched. 7 | Table bool // Table is a flag that indicates if the output should be in table format. 8 | Description bool // Description is a flag that indicates if the description of the repository should be fetched. 9 | Rows int // Rows is the number of rows to show. 10 | MinStar int // MinStar is the minimum number of stars to show. 11 | Search string // Search is the search code at dependents (repositories/packages). 12 | Token string // Token is the GitHub token to use for authenticated requests. 13 | OutputFile string // Output is the file to write founds to. 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 z3r0yu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue report 3 | about: Create a report to help us to improve the project 4 | labels: 'Type: Bug' 5 | 6 | --- 7 | 8 | 11 | 12 | 13 | 14 | ### ghtracker version: 15 | 16 | 17 | 18 | 19 | ### Current Behavior: 20 | 21 | 22 | ### Expected Behavior: 23 | 24 | 25 | ### Steps To Reproduce: 26 | 31 | 32 | 33 | ### Anything else: 34 | -------------------------------------------------------------------------------- /pkg/runner/outputer.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "errors" 5 | jsoniter "github.com/json-iterator/go" 6 | "io" 7 | "os" 8 | "path/filepath" 9 | ) 10 | 11 | // OutputWriter outputs content to writers. 12 | type OutputWriter struct { 13 | JSON bool 14 | } 15 | 16 | // NewOutputWriter creates a new OutputWriter 17 | func NewOutputWriter(json bool) *OutputWriter { 18 | return &OutputWriter{JSON: json} 19 | } 20 | 21 | func (o *OutputWriter) createFile(filename string, appendToFile bool) (*os.File, error) { 22 | if filename == "" { 23 | return nil, errors.New("empty filename") 24 | } 25 | 26 | dir := filepath.Dir(filename) 27 | 28 | if dir != "" { 29 | if _, err := os.Stat(dir); os.IsNotExist(err) { 30 | err := os.MkdirAll(dir, os.ModePerm) 31 | if err != nil { 32 | return nil, err 33 | } 34 | } 35 | } 36 | 37 | var file *os.File 38 | var err error 39 | if appendToFile { 40 | file, err = os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 41 | } else { 42 | file, err = os.Create(filename) 43 | } 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | return file, nil 49 | } 50 | 51 | func (o *OutputWriter) writeJSONResults(results interface{}, writer io.Writer) error { 52 | encoder := jsoniter.NewEncoder(writer) 53 | err := encoder.Encode(results) 54 | if err != nil { 55 | return err 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /pkg/runner/engine_test.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAlreadyAdded(t *testing.T) { 8 | repos := []GHRepo{ 9 | {URL: "https://github.com/zer0yu/ghtopdep"}, 10 | {URL: "https://github.com/projectdiscovery/gologger"}, 11 | } 12 | 13 | if !alreadyAdded("https://github.com/zer0yu/ghtopdep", repos) { 14 | t.Errorf("alreadyAdded function failed, expected %v, got %v", true, false) 15 | } 16 | 17 | if alreadyAdded("https://github.com/nonexistent/repo", repos) { 18 | t.Errorf("alreadyAdded function failed, expected %v, got %v", false, true) 19 | } 20 | } 21 | 22 | func TestSortRepos(t *testing.T) { 23 | repos := []GHRepo{ 24 | {URL: "https://github.com/zer0yu/ghtopdep", Stars: 10}, 25 | {URL: "https://github.com/projectdiscovery/gologger", Stars: 20}, 26 | } 27 | 28 | sortedRepos := sortRepos(repos, 2) 29 | 30 | if sortedRepos[0].Stars != 20 { 31 | t.Errorf("sortRepos function failed, expected %v, got %v", 20, sortedRepos[0].Stars) 32 | } 33 | 34 | if sortedRepos[1].Stars != 10 { 35 | t.Errorf("sortRepos function failed, expected %v, got %v", 10, sortedRepos[1].Stars) 36 | } 37 | } 38 | 39 | func TestReadableStars(t *testing.T) { 40 | repos := []GHRepo{ 41 | {Stars: 999}, 42 | {Stars: 1000}, 43 | {Stars: 9999}, 44 | {Stars: 10000}, 45 | {Stars: 999999}, 46 | {Stars: 1000000}, 47 | } 48 | 49 | expected := []string{"999", "1.0K", "10.0K", "10K", "1000K", "1000000"} 50 | 51 | repos4show := readableStars(repos) 52 | 53 | for i, repo := range repos4show { 54 | if repo.Stars != expected[i] { 55 | t.Errorf("readableStars function failed, expected %v, got %v", expected[i], repo.Stars) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/projectdiscovery/gologger" 5 | "github.com/zer0yu/ghtracker/pkg/runner" 6 | "os" 7 | 8 | "github.com/spf13/cobra" 9 | "github.com/zer0yu/ghtracker/pkg/options" 10 | ) 11 | 12 | var ghoptions = options.GHTopDepOptions{} 13 | 14 | var rootCmd = &cobra.Command{ 15 | Use: "ghtracker", 16 | Short: "CLI tool for tracking dependents repositories and sorting result by Stars", 17 | Long: ``, 18 | Run: GHTracker, 19 | } 20 | 21 | func Execute() { 22 | err := rootCmd.Execute() 23 | if err != nil { 24 | os.Exit(1) 25 | } 26 | } 27 | 28 | func init() { 29 | rootCmd.PersistentFlags().BoolVarP(&ghoptions.Version, "version", "v", false, "Show version of ghtracker") 30 | rootCmd.PersistentFlags().StringVarP(&ghoptions.URL, "url", "u", "", "URL to process") 31 | rootCmd.PersistentFlags().BoolVarP(&ghoptions.Repositories, "repositories", "r", false, 32 | "Sort repositories or packages (default repositories)") 33 | rootCmd.PersistentFlags().BoolVarP(&ghoptions.Table, "table", "t", false, "View mode") 34 | rootCmd.PersistentFlags().BoolVarP(&ghoptions.Description, "description", "d", false, 35 | "Show description of packages or repositories (performs additional request per repository)") 36 | rootCmd.PersistentFlags().IntVarP(&ghoptions.Rows, "rows", "o", 10, "Number of showing repositories (default=10)") 37 | rootCmd.PersistentFlags().IntVarP(&ghoptions.MinStar, "minstar", "m", 5, "Minimum number of stars (default=5)") 38 | rootCmd.PersistentFlags().StringVarP(&ghoptions.Search, "search", "s", "", 39 | "search code at dependents (repositories or packages)") 40 | rootCmd.PersistentFlags().StringVarP(&ghoptions.Token, "token", "k", os.Getenv("GHTOPDEP_TOKEN"), "GitHub token") 41 | rootCmd.PersistentFlags().StringVarP(&ghoptions.OutputFile, "output", "f", "", "File to write output to (optional)") 42 | } 43 | 44 | func GHTracker(_ *cobra.Command, _ []string) { 45 | newRunner, err := runner.NewRunner(&ghoptions) 46 | if err != nil { 47 | gologger.Fatal().Msgf("Could not create runner: %s\n", err) 48 | } 49 | 50 | err = newRunner.RunGHCrawler() 51 | if err != nil { 52 | gologger.Fatal().Msgf("Could not run fuzz engine: %s\n", err) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zer0yu/ghtracker 2 | 3 | go 1.21.3 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.8.1 7 | github.com/briandowns/spinner v1.23.0 8 | github.com/gofri/go-github-ratelimit v1.1.0 9 | github.com/google/go-github/v58 v58.0.0 10 | github.com/imroc/req/v3 v3.42.3 11 | github.com/olekukonko/tablewriter v0.0.5 12 | github.com/projectdiscovery/gologger v1.1.12 13 | github.com/spf13/cobra v1.8.0 14 | ) 15 | 16 | require ( 17 | github.com/andybalholm/brotli v1.0.6 // indirect 18 | github.com/andybalholm/cascadia v1.3.2 // indirect 19 | github.com/cloudflare/circl v1.3.7 // indirect 20 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect 21 | github.com/fatih/color v1.7.0 // indirect 22 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect 23 | github.com/golang/snappy v0.0.4 // indirect 24 | github.com/google/go-querystring v1.1.0 // indirect 25 | github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect 26 | github.com/hashicorp/errwrap v1.1.0 // indirect 27 | github.com/hashicorp/go-multierror v1.1.1 // indirect 28 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 29 | github.com/json-iterator/go v1.1.12 // indirect 30 | github.com/klauspost/compress v1.17.4 // indirect 31 | github.com/klauspost/pgzip v1.2.5 // indirect 32 | github.com/logrusorgru/aurora v2.0.3+incompatible // indirect 33 | github.com/mattn/go-colorable v0.1.2 // indirect 34 | github.com/mattn/go-isatty v0.0.8 // indirect 35 | github.com/mattn/go-runewidth v0.0.9 // indirect 36 | github.com/mholt/archiver/v3 v3.5.1 // indirect 37 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect 38 | github.com/modern-go/reflect2 v1.0.2 // indirect 39 | github.com/nwaples/rardecode v1.1.3 // indirect 40 | github.com/onsi/ginkgo/v2 v2.13.2 // indirect 41 | github.com/pierrec/lz4/v4 v4.1.2 // indirect 42 | github.com/quic-go/qpack v0.4.0 // indirect 43 | github.com/quic-go/qtls-go1-20 v0.4.1 // indirect 44 | github.com/quic-go/quic-go v0.40.1 // indirect 45 | github.com/refraction-networking/utls v1.6.0 // indirect 46 | github.com/spf13/pflag v1.0.5 // indirect 47 | github.com/ulikunitz/xz v0.5.11 // indirect 48 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect 49 | go.uber.org/mock v0.4.0 // indirect 50 | golang.org/x/crypto v0.18.0 // indirect 51 | golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect 52 | golang.org/x/mod v0.14.0 // indirect 53 | golang.org/x/net v0.20.0 // indirect 54 | golang.org/x/sys v0.16.0 // indirect 55 | golang.org/x/term v0.16.0 // indirect 56 | golang.org/x/text v0.14.0 // indirect 57 | golang.org/x/tools v0.16.1 // indirect 58 | gopkg.in/djherbis/times.v1 v1.3.0 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /pkg/runner/runner.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/briandowns/spinner" 7 | "github.com/gofri/go-github-ratelimit/github_ratelimit" 8 | "github.com/google/go-github/v58/github" 9 | "github.com/imroc/req/v3" 10 | "github.com/projectdiscovery/gologger" 11 | "github.com/zer0yu/ghtracker/pkg/options" 12 | "io" 13 | "os" 14 | "time" 15 | ) 16 | 17 | type GHRunner struct { 18 | options *options.GHTopDepOptions 19 | ghClient *GHClient 20 | reqClient *req.Client 21 | } 22 | 23 | type GHClient struct { 24 | gitClient *github.Client 25 | ghctx context.Context 26 | } 27 | 28 | func NewRunner(options *options.GHTopDepOptions) (*GHRunner, error) { 29 | ghRunner := &GHRunner{options: options} 30 | if options.Version { 31 | showBanner() 32 | os.Exit(0) 33 | } 34 | 35 | if options.URL == "" { 36 | gologger.Fatal().Msgf("URL is empty!") 37 | } 38 | 39 | if (options.Description || options.Search != "") && options.Token != "" { 40 | rateLimiter, err := github_ratelimit.NewRateLimitWaiterClient(nil) 41 | if err != nil { 42 | gologger.Error().Msgf("Init rateLimiter Error: %v\n", err) 43 | } 44 | 45 | // Set GitHub authentication information and configure the rate limit 46 | ghRunner.ghClient = &GHClient{ 47 | gitClient: nil, 48 | ghctx: context.Background(), 49 | } 50 | 51 | ghRunner.ghClient.gitClient = github.NewClient(rateLimiter).WithAuthToken(options.Token) 52 | 53 | // verify token 54 | _, resp, err := ghRunner.ghClient.gitClient.Users.Get(ghRunner.ghClient.ghctx, "") 55 | if err != nil { 56 | //fmt.Printf("\nerror: %v\n", err) 57 | gologger.Error().Msgf("Token is invalid!") 58 | } 59 | 60 | // If a Token Expiration has been set, it will be displayed. 61 | if !resp.TokenExpiration.IsZero() { 62 | gologger.Error().Msgf("Token Expiration: %v\n", resp.TokenExpiration) 63 | } 64 | } else if (options.Description || options.Search != "") && options.Token == "" { 65 | gologger.Error().Msgf("Please provide token!") 66 | } 67 | 68 | // set req client 69 | ghRunner.reqClient = req.C(). 70 | EnableDumpEachRequest(). 71 | OnAfterResponse(func(client *req.Client, resp *req.Response) error { 72 | if resp.Err != nil { // Ignore when there is an underlying error, e.g. network error. 73 | return nil 74 | } 75 | // Treat non-successful responses as errors, record raw dump content in error message. 76 | if !resp.IsSuccessState() { // Status code is not between 200 and 299. 77 | resp.Err = fmt.Errorf("bad response, raw content:\n%s", resp.Dump()) 78 | } 79 | return nil 80 | }) 81 | ghRunner.reqClient.ImpersonateChrome() 82 | ghRunner.reqClient.R(). 83 | SetRetryCount(15). 84 | SetRetryBackoffInterval(10*time.Second, 20*time.Second). 85 | SetRetryFixedInterval(2 * time.Second) 86 | 87 | return ghRunner, nil 88 | } 89 | 90 | func (r *GHRunner) RunGHCrawler() error { 91 | s := spinner.New(spinner.CharSets[9], 100*time.Millisecond) 92 | s.Suffix = "\n" 93 | s.FinalMSG = "Complete!\n" 94 | s.Start() 95 | outputs := []io.Writer{} 96 | if r.options.OutputFile != "" { 97 | outputWriter := NewOutputWriter(true) 98 | file, err := outputWriter.createFile(r.options.OutputFile, true) 99 | if err != nil { 100 | gologger.Error().Msgf("Could not create file %s: %s\n", r.options.OutputFile, err) 101 | return err 102 | } 103 | err = r.GHCrawlEngine(append(outputs, file)) 104 | if err != nil { 105 | gologger.Error().Msgf("Run GHCrawlEngine Error!") 106 | s.Stop() 107 | return err 108 | } 109 | file.Close() 110 | } else { 111 | err := r.GHCrawlEngine(outputs) 112 | if err != nil { 113 | gologger.Error().Msgf("Run GHCrawlEngine Error!") 114 | s.Stop() 115 | return err 116 | } 117 | } 118 | 119 | s.Stop() 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
15 | Features • 16 | Install • 17 | Usage • 18 | Why ghtracker • 19 | References • 20 |
21 | 22 | --- 23 | 24 | `ghtracker` is a tool for tracking dependents repositories and sorting result by Stars ⭐. It has a simple architecture and is optimized for speed. 25 | 26 | ## Features 27 | 28 | - Analysis of 'used by' or 'Dependency graph' in Github repositories and sorting result by Stars 29 | - In-depth Search of Key Code in Used Objects 30 | - Support for Output in JSON Format 31 | - Provision of Aesthetically Pleasing and Easy-to-Read Terminal Table Outputs 32 | - Comprehensive Cross-Platform Compatibility 33 | - Efficient HTTP Request Anti-Ban Mechanism 34 | 35 | ## Installation 36 | 37 | ### From Source 38 | 39 | `ghtracker` requires **go1.20** to install successfully. Run the following command to install the latest version: 40 | 41 | ```sh 42 | go install github.com/zer0yu/ghtracker@latest 43 | ``` 44 | 45 | ### From Release 46 | 47 | Download from [releases](http://github.com/zer0yu/ghtracker/releases/) 48 | 49 | ## Usage 50 | 51 | ### Help 52 | 53 | ```sh 54 | ghtracker -h 55 | ``` 56 | 57 | This will display help for the tool. Here are all the switches it supports. 58 | 59 | ```sh 60 | CLI tool for tracking dependents repositories and sorting result by Stars 61 | 62 | Usage: 63 | ghtracker [flags] 64 | 65 | Flags: 66 | -d, --description Show description of packages or repositories (performs additional request per repository) 67 | -h, --help help for ghtracker 68 | -m, --minstar int Minimum number of stars (default=5) (default 5) 69 | -f, --output string File to write output to (optional) 70 | -r, --repositories Sort repositories or packages (default packages) 71 | -o, --rows int Number of showing repositories (default=10) (default 10) 72 | -s, --search string search code at dependents (repositories or packages) 73 | -t, --table View mode 74 | -k, --token string GitHub token 75 | -u, --url string URL to process 76 | ``` 77 | 78 | ### Basic Usage 79 | 80 | #### 1. Retrieves Packages in 'Used by' or 'Dependency graph' by default, and saves the addresses of projects with more than 5 output stars. 81 | 82 | ```sh 83 | ghtracker --url https://github.com/AFLplusplus/LibAFL -t 84 | / 85 | +---------------------------------------+-------+-------------+ 86 | | URL | STARS | DESCRIPTION | 87 | +---------------------------------------+-------+-------------+ 88 | | https://github.com/AFLplusplus/LibAFL | 1.7K | | 89 | | https://github.com/epi052/feroxfuzz | 183 | | 90 | | https://github.com/fkie-cad/butterfly | 40 | | 91 | | https://github.com/z2-2z/peacock | 0 | | 92 | +---------------------------------------+-------+-------------+ 93 | found 8 packages others packages are private 94 | Complete! 95 | ``` 96 | 97 | #### 2. Get the Repositories in the 'Used by' or 'Dependency graph', by default it saves the addresses of projects with the first 10. 98 | 99 | ```sh 100 | ghtracker --url https://github.com/AFLplusplus/LibAFL -t -r 101 | / 102 | +--------------------------------------------------------+-------+-------------+ 103 | | URL | STARS | DESCRIPTION | 104 | +--------------------------------------------------------+-------+-------------+ 105 | | https://github.com/AFLplusplus/LibAFL | 1.7K | | 106 | | https://github.com/hardik05/Damn_Vulnerable_C_Program | 604 | | 107 | | https://github.com/fuzzland/ityfuzz | 524 | | 108 | | https://github.com/epi052/feroxfuzz | 183 | | 109 | | https://github.com/epi052/fuzzing-101-solutions | 119 | | 110 | | https://github.com/tlspuffin/tlspuffin | 117 | | 111 | | https://github.com/Agnoctopus/Tartiflette | 90 | | 112 | | https://github.com/fkie-cad/butterfly | 40 | | 113 | | https://github.com/IntelLabs/PreSiFuzz | 38 | | 114 | | https://github.com/RickdeJager/TrackmaniaFuzzer | 32 | | 115 | | https://github.com/AFLplusplus/libafl_paper_artifacts | 17 | | 116 | | https://github.com/novafacing/libc-fuzzer | 12 | | 117 | | https://github.com/vusec/triereme | 11 | | 118 | | https://github.com/bitterbit/fuzzer-qemu | 7 | | 119 | | https://github.com/atredis-jordan/libafl-workshop-blog | 7 | | 120 | | https://github.com/rezer0dai/LibAFL | 6 | | 121 | | https://github.com/jjjutla/Fuzz | 5 | | 122 | +--------------------------------------------------------+-------+-------------+ 123 | found 190 repositories others repositories are private 124 | Complete! 125 | ``` 126 | 127 | #### 3. Save the result in a file in Json format 128 | 129 | ```sh 130 | ghtracker --url https://github.com/AFLplusplus/LibAFL -t -r --output ./test.json 131 | ``` 132 | 133 | #### 4. Search Code Pattern in 'Dependency graph' (Need Github Token) 134 | 135 | ```sh 136 | ghtracker --url https://github.com/AFLplusplus/LibAFL --token your_token_value -t --search AFL --output ./test.json 137 | \ 138 | [INF] https://github.com/AFLplusplus/LibAFL/blob/e117b7199ca902d462edc1de1bc0b3cb71c27aff/scripts/afl-persistent-config with 1747 stars 139 | [INF] https://github.com/AFLplusplus/LibAFL/blob/e117b7199ca902d462edc1de1bc0b3cb71c27aff/libafl_cc/src/afl-coverage-pass.cc with 1747 stars 140 | [INF] https://github.com/AFLplusplus/LibAFL/blob/e117b7199ca902d462edc1de1bc0b3cb71c27aff/libafl_targets/src/cmps/observers/aflpp.rs with 1747 stars 141 | [INF] https://github.com/AFLplusplus/LibAFL/blob/e117b7199ca902d462edc1de1bc0b3cb71c27aff/fuzzers/forkserver_libafl_cc/src/bin/libafl_cc.rs with 1747 stars 142 | [INF] https://github.com/AFLplusplus/LibAFL/blob/e117b7199ca902d462edc1de1bc0b3cb71c27aff/fuzzers/libfuzzer_libpng_aflpp_ui/README.md with 1747 stars 143 | ... 144 | ... 145 | ``` 146 | 147 | #### 5. Get all repositories in the 'Dependency graph' 148 | 149 | ```sh 150 | ghtracker --url https://github.com/AFLplusplus/LibAFL -t -r -m 0 --output ./test.json 151 | ``` 152 | 153 | # Why ghtracker 154 | 155 | 1. Gtihub does not support sorting the 'Dependency graph' by the number of Stars from 2019 until now, so ghtracker 156 | was born. Detials in issue in [1537](https://github.com/isaacs/github/issues/1537) 157 | 2. GHTOPDEP does not continue to update support, and there are some issues in the community, e.g., [issue](https://github.com/github-tooling/ghtopdep). ghtracker has optimized and improved on this, and has addressed issues in the community. 158 | 159 | # References 160 | - [GHTOPDEP](https://github.com/github-tooling/ghtopdep) 161 | -------------------------------------------------------------------------------- /pkg/runner/engine.go: -------------------------------------------------------------------------------- 1 | package runner 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/PuerkitoBio/goquery" 8 | "github.com/google/go-github/v58/github" 9 | "github.com/olekukonko/tablewriter" 10 | "github.com/projectdiscovery/gologger" 11 | "github.com/zer0yu/ghtracker/pkg/config" 12 | "github.com/zer0yu/ghtracker/pkg/utils/textwrap" 13 | "io" 14 | "math" 15 | "net/url" 16 | "os" 17 | "sort" 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | func (r *GHRunner) GHCrawlEngine(writers []io.Writer) error { 23 | destination := "repository" 24 | destinations := "repositories" 25 | repositories := r.options.Repositories // Assuming repositories is a list of strings 26 | 27 | if !repositories { 28 | destination = "package" 29 | destinations = "packages" 30 | } 31 | // 32 | var repos []GHRepo 33 | moreThanZeroCount := 0 34 | totalReposCount := 0 35 | 36 | pageURL, _ := r.getPageUrl(destination) 37 | //fmt.Println(pageURL) 38 | for { 39 | resp, err := r.reqClient.R().Get(pageURL) 40 | if err != nil { 41 | gologger.Error().Msgf("Req pageURL Error!") 42 | } 43 | doc, _ := goquery.NewDocumentFromReader(resp.Body) 44 | dependents := doc.Find(config.ItemSelector) 45 | totalReposCount += dependents.Length() 46 | 47 | dependents.Each(func(i int, s *goquery.Selection) { 48 | repoStarsList := s.Find(config.StarsSelector) 49 | if repoStarsList.Length() > 0 { 50 | repoStars := strings.Replace(repoStarsList.First().Text(), ",", "", -1) 51 | repoStarsNum, _ := strconv.Atoi(strings.TrimSpace(repoStars)) 52 | 53 | if repoStarsNum != 0 { 54 | moreThanZeroCount++ 55 | } 56 | 57 | if repoStarsNum >= r.options.MinStar { 58 | relativeRepoURL, _ := s.Find(config.RepoSelector).First().Attr("href") 59 | repoURL := fmt.Sprintf("%s%s", config.GithubURL, relativeRepoURL) 60 | 61 | if !alreadyAdded(repoURL, repos) && repoURL != pageURL { 62 | if r.options.Description { 63 | repoDescription, _ := r.fetchDescription(r.ghClient.ghctx, relativeRepoURL) 64 | repos = append(repos, GHRepo{URL: repoURL, Stars: repoStarsNum, 65 | Description: repoDescription}) 66 | } else { 67 | repos = append(repos, GHRepo{URL: repoURL, Stars: repoStarsNum}) 68 | } 69 | } 70 | } 71 | } 72 | }) 73 | 74 | node := doc.Find(config.NextButtonSelector) 75 | if node.Length() == 2 { 76 | pageURL, _ = node.Eq(1).Attr("href") 77 | } else if node.Length() == 0 || node.First().Text() == "Previous" { 78 | break 79 | } else if node.First().Text() == "Next" { 80 | pageURL, _ = node.First().Attr("href") 81 | } 82 | } 83 | 84 | sortedRepos := sortRepos(repos, totalReposCount) 85 | 86 | if r.options.Search != "" { 87 | for _, repo := range repos { 88 | u, _ := url.Parse(repo.URL) 89 | repoPath := strings.TrimPrefix(u.Path, "/") 90 | query := fmt.Sprintf("%s repo:%s", r.options.Search, repoPath) 91 | results, _, _ := r.ghClient.gitClient.Search.Code(r.ghClient.ghctx, query, nil) 92 | 93 | for _, s := range results.CodeResults { 94 | gologger.Info().Msgf("%s with %d stars\n", *s.HTMLURL, repo.Stars) 95 | } 96 | 97 | outputWriter := NewOutputWriter(true) 98 | for _, writer := range writers { 99 | err := outputWriter.writeJSONResults(results.CodeResults, writer) 100 | if err != nil { 101 | gologger.Error().Msgf("Could not write results for %s: %s\n", query, err) 102 | } 103 | } 104 | 105 | } 106 | } else { 107 | r.showResult(sortedRepos, totalReposCount, moreThanZeroCount, destinations, writers) 108 | } 109 | 110 | return nil 111 | } 112 | 113 | type GHPackage struct { 114 | Count int 115 | PackageID string 116 | } 117 | 118 | type GHRepo struct { 119 | URL string 120 | Stars int 121 | Description string 122 | } 123 | 124 | type GHRepo4Show struct { 125 | URL string 126 | Stars string 127 | Description string 128 | } 129 | 130 | func (r *GHRunner) getPageUrl(destination string) (string, error) { 131 | pageURL := fmt.Sprintf("%s/network/dependents?dependent_type=%s", r.options.URL, strings.ToUpper(destination)) 132 | resp, err := r.reqClient.R().Get(pageURL) 133 | if err != nil { 134 | gologger.Error().Msgf("Req pageURL Error!") 135 | } 136 | doc, err := goquery.NewDocumentFromReader(resp.Body) 137 | if err != nil { // Append raw dump content to error message if goquery parse failed to help troubleshoot. 138 | gologger.Error().Msgf("failed to parse html: %s, raw content:\n%s", err.Error(), resp.Dump()) 139 | } 140 | link := doc.Find(".select-menu-item") 141 | if link.Length() > 0 { 142 | packages := make([]GHPackage, 0) 143 | link.Each(func(i int, s *goquery.Selection) { 144 | href, _ := s.Attr("href") 145 | repoURL := fmt.Sprintf("https://github.com/%s", href) 146 | resp, err := r.reqClient.R().Get(repoURL) 147 | if err != nil { 148 | // 处理错误 149 | gologger.Error().Msgf("Req repoURL Error!") 150 | } 151 | parsedItem, err := goquery.NewDocumentFromReader(resp.Body) 152 | if err != nil { 153 | gologger.Error().Msgf("failed to parse html: %s, raw content:\n%s", err.Error(), resp.Dump()) 154 | } 155 | hrefURL, _ := url.Parse(repoURL) 156 | packageID := strings.Split(hrefURL.RawQuery, "=")[1] 157 | 158 | countStr := parsedItem.Find(".table-list-filters a:first-child").First().Text() 159 | countStr = strings.Split(countStr, " ")[0] 160 | countStr = strings.ReplaceAll(countStr, ",", "") 161 | count, _ := strconv.Atoi(countStr) 162 | 163 | packages = append(packages, GHPackage{ 164 | Count: count, 165 | PackageID: packageID, 166 | }) 167 | }) 168 | sort.Slice(packages, func(i, j int) bool { 169 | return packages[i].Count > packages[j].Count 170 | }) 171 | 172 | mostPopularPackageID := packages[0].PackageID 173 | pageURL = fmt.Sprintf("%s/network/dependents?dependent_type=%s&package_id=%s", r.options.URL, 174 | strings.ToUpper(destination), mostPopularPackageID) 175 | } 176 | return pageURL, nil 177 | } 178 | 179 | // fetchDescription fetches the description of a repository 180 | func (r *GHRunner) fetchDescription(ctx context.Context, relativeURL string) (string, error) { 181 | parts := strings.Split(relativeURL, "/") 182 | if len(parts) < 2 { 183 | return "", fmt.Errorf("invalid relative URL") 184 | } 185 | owner, repo := parts[0], parts[1] 186 | repoinfo, _, err := r.ghClient.gitClient.Repositories.Get(ctx, owner, repo) 187 | if _, ok := err.(*github.RateLimitError); ok { 188 | gologger.Error().Msgf("hit rate limit") 189 | } 190 | //fmt.Println(repoinfo.GetDescription()) 191 | if repoinfo.GetDescription() != "" { 192 | return textwrap.Shorten(repoinfo.GetDescription(), 60), nil 193 | } 194 | 195 | return " ", nil 196 | } 197 | 198 | func alreadyAdded(repoURL string, repos []GHRepo) bool { 199 | for _, repo := range repos { 200 | if repo.URL == repoURL { 201 | return true 202 | } 203 | } 204 | return false 205 | } 206 | 207 | func sortRepos(repos []GHRepo, rows int) []GHRepo { 208 | sort.Slice(repos, func(i, j int) bool { 209 | return repos[i].Stars > repos[j].Stars 210 | }) 211 | 212 | if rows > len(repos) { 213 | rows = len(repos) 214 | } 215 | return repos[:rows] 216 | } 217 | 218 | // showResult shows the result of the search or fmt the result in table format 219 | func (r *GHRunner) showResult(repos []GHRepo, totalReposCount int, moreThanZeroCount int, destinations string, writers []io.Writer) { 220 | if r.options.Table { 221 | if len(repos) > 0 { 222 | repos4show := readableStars(repos) 223 | tw := tablewriter.NewWriter(os.Stdout) 224 | tw.SetHeader([]string{"URL", "Stars", "Description"}) 225 | for _, repo := range repos4show { 226 | tw.Append([]string{repo.URL, repo.Stars, repo.Description}) 227 | } 228 | tw.Render() 229 | fmt.Printf("found %d %s others %s are private\n", totalReposCount, destinations, destinations) 230 | fmt.Printf("found %d %s with more than zero star\n", moreThanZeroCount, destinations) 231 | } else { 232 | fmt.Printf("Doesn't find any %s that match search request\n", destinations) 233 | } 234 | } else { 235 | reposJSON, _ := json.Marshal(readableStars(repos)) 236 | fmt.Println(string(reposJSON)) 237 | } 238 | 239 | outputWriter := NewOutputWriter(true) 240 | for _, writer := range writers { 241 | err := outputWriter.writeJSONResults(readableStars(repos), writer) 242 | if err != nil { 243 | gologger.Error().Msgf("Could not write results for %s: %s\n", r.options.OutputFile, err) 244 | } 245 | } 246 | 247 | } 248 | 249 | func humanize(num int) string { 250 | if num < 1000 { 251 | return fmt.Sprintf("%d", num) 252 | } else if num < 10000 { 253 | return fmt.Sprintf("%.1fK", math.Round(float64(num)/100.0)/10.0) 254 | } else if num < 1000000 { 255 | return fmt.Sprintf("%.0fK", math.Round(float64(num)/1000.0)) 256 | } else { 257 | return fmt.Sprintf("%d", num) 258 | } 259 | } 260 | 261 | func readableStars(repos []GHRepo) []GHRepo4Show { 262 | repos4show := make([]GHRepo4Show, 0) 263 | for i := range repos { 264 | repos4show = append(repos4show, GHRepo4Show{URL: repos[i].URL, Stars: humanize(repos[i].Stars), Description: repos[i].Description}) 265 | } 266 | return repos4show 267 | } 268 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= 2 | github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= 3 | github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= 4 | github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= 5 | github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= 6 | github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= 7 | github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= 8 | github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= 9 | github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= 10 | github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= 11 | github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= 12 | github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= 13 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= 18 | github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= 19 | github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= 20 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 21 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 22 | github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY= 23 | github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 24 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 25 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 26 | github.com/gofri/go-github-ratelimit v1.1.0 h1:ijQ2bcv5pjZXNil5FiwglCg8wc9s8EgjTmNkqjw8nuk= 27 | github.com/gofri/go-github-ratelimit v1.1.0/go.mod h1:OnCi5gV+hAG/LMR7llGhU7yHt44se9sYgKPnafoL7RY= 28 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 29 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 30 | github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 31 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 32 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 33 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 34 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 35 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 36 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 37 | github.com/google/go-github/v58 v58.0.0 h1:Una7GGERlF/37XfkPwpzYJe0Vp4dt2k1kCjlxwjIvzw= 38 | github.com/google/go-github/v58 v58.0.0/go.mod h1:k4hxDKEfoWpSqFlc8LTpGd9fu2KrV1YAa6Hi6FmDNY4= 39 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 40 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 41 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 42 | github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8= 43 | github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= 44 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 45 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= 46 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 47 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= 48 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= 49 | github.com/imroc/req/v3 v3.42.3 h1:ryPG2AiwouutAopwPxKpWKyxgvO8fB3hts4JXlh3PaE= 50 | github.com/imroc/req/v3 v3.42.3/go.mod h1:Axz9Y/a2b++w5/Jht3IhQsdBzrG1ftJd1OJhu21bB2Q= 51 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 52 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 53 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 54 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 55 | github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= 56 | github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= 57 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 58 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 59 | github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= 60 | github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= 61 | github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= 62 | github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= 63 | github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 64 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 65 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 66 | github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= 67 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 68 | github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= 69 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 70 | github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= 71 | github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= 72 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 73 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 74 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 75 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 76 | github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 77 | github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= 78 | github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= 79 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 80 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 81 | github.com/onsi/ginkgo/v2 v2.13.2 h1:Bi2gGVkfn6gQcjNjZJVO8Gf0FHzMPf2phUei9tejVMs= 82 | github.com/onsi/ginkgo/v2 v2.13.2/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= 83 | github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= 84 | github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= 85 | github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM= 86 | github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= 87 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 88 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 89 | github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A= 90 | github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw= 91 | github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= 92 | github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= 93 | github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= 94 | github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= 95 | github.com/quic-go/quic-go v0.40.1 h1:X3AGzUNFs0jVuO3esAGnTfvdgvL4fq655WaOi1snv1Q= 96 | github.com/quic-go/quic-go v0.40.1/go.mod h1:PeN7kuVJ4xZbxSv/4OX6S1USOX8MJvydwpTx31vx60c= 97 | github.com/refraction-networking/utls v1.6.0 h1:X5vQMqVx7dY7ehxxqkFER/W6DSjy8TMqSItXm8hRDYQ= 98 | github.com/refraction-networking/utls v1.6.0/go.mod h1:kHJ6R9DFFA0WsRgBM35iiDku4O7AqPR6y79iuzW7b10= 99 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 100 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 101 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 102 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 103 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 104 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 105 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 106 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 107 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 108 | github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 109 | github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 110 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 111 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 112 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= 113 | github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= 114 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 115 | go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= 116 | go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= 117 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 118 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 119 | golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= 120 | golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= 121 | golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= 122 | golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 123 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 124 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 125 | golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= 126 | golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 127 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 128 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 129 | golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 130 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 131 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 132 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 133 | golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= 134 | golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= 135 | golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= 136 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 139 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 141 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 143 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 144 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 145 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 146 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 147 | golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 148 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 149 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 150 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 151 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 152 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 153 | golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= 154 | golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= 155 | golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= 156 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 157 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 158 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 159 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 160 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 161 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 162 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 163 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 164 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 165 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 166 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 167 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 168 | golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= 169 | golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= 170 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 171 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 172 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 173 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 174 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 175 | gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= 176 | gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= 177 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 178 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 179 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 180 | --------------------------------------------------------------------------------