From fa45bc1b12b39e86cbfdefb9f98a18ceb964b5f3 Mon Sep 17 00:00:00 2001 From: Jaime Pillora Date: Mon, 1 Jan 2024 11:34:17 +1100 Subject: [PATCH] support single bz2 files, add ddg search --- README.md | 4 ++-- handler/handler.go | 7 +++++-- handler/handler_execute.go | 21 ++++++++++++--------- handler/search.go | 30 +++++++++++++++++++++--------- handler/strings.go | 2 +- handler/strings_test.go | 22 ++++++++++++++++++++++ scripts/install.sh.tmpl | 7 +++++-- scripts/install.txt.tmpl | 2 +- 8 files changed, 69 insertions(+), 26 deletions(-) create mode 100644 handler/strings_test.go diff --git a/README.md b/README.md index 507c0ac..d291eaa 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ curl https://i.jpillora.com//@! | bash ``` ```sh -# search Google for github repo +# search web for github repo curl https://i.jpillora.com/! | bash ``` @@ -24,7 +24,7 @@ curl https://i.jpillora.com/! | bash **Path API** -* `user` Github user (defaults to @jpillora, customisable if you [host your own](#host-your-own), uses Google to pick most relevant `user` when `repo` not found) +* `user` Github user (defaults to @jpillora, customisable if you [host your own](#host-your-own), searches the web to pick most relevant `user` when `repo` not found) * `repo` Github repository belonging to `user` (**required**) * `release` Github release name (defaults to the **latest** release) * `!` When provided, downloads binary directly into `/usr/local/bin/` (defaults to working directory) diff --git a/handler/handler.go b/handler/handler.go index 2c2a41c..76c0db5 100644 --- a/handler/handler.go +++ b/handler/handler.go @@ -32,7 +32,7 @@ var ( type Query struct { User, Program, AsProgram, Release string - MoveToPath, Google, Insecure bool + MoveToPath, Search, Insecure bool SudoMove bool // deprecated: not used, now automatically detected } @@ -127,7 +127,10 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if q.Program == "" { q.Program = q.User q.User = h.Config.User - q.Google = true + q.Search = true + } + if q.Release == "" { + q.Release = "latest" } // micro > nano! if q.User == "" && q.Program == "micro" { diff --git a/handler/handler_execute.go b/handler/handler_execute.go index f95efdc..f04292c 100644 --- a/handler/handler_execute.go +++ b/handler/handler_execute.go @@ -27,15 +27,15 @@ func (h *Handler) execute(q Query) (Result, error) { ts := time.Now() release, assets, err := h.getAssetsNoCache(q) if err == nil { - //didn't need google - q.Google = false - } else if errors.Is(err, errNotFound) && q.Google { - //use google to auto-detect user... - user, program, gerr := searchGoogle(q.Program) + //didn't need search + q.Search = false + } else if errors.Is(err, errNotFound) && q.Search { + //use ddg/google to auto-detect user... + user, program, gerr := imFeelingLuck(q.Program) if gerr != nil { - log.Printf("google search failed: %s", gerr) + log.Printf("web search failed: %s", gerr) } else { - log.Printf("google search found: %s/%s", user, program) + log.Printf("web search found: %s/%s", user, program) if program != q.Program { log.Printf("program mismatch: got %s: expected %s", q.Program, program) } @@ -75,7 +75,7 @@ func (h *Handler) getAssetsNoCache(q Query) (string, Assets, error) { log.Printf("fetching asset info for %s/%s@%s", user, repo, release) url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", user, repo) ghas := ghAssets{} - if release == "" { + if release == "" || release == "latest" { url += "/latest" ghr := ghRelease{} if err := h.get(url, &ghr); err != nil { @@ -120,7 +120,10 @@ func (h *Handler) getAssetsNoCache(q Query) (string, Assets, error) { if fext == "" && ga.Size > 1024*1024 { fext = ".bin" // +1MB binary } - if fext != ".bin" && fext != ".zip" && fext != ".gz" && fext != ".tar.gz" && fext != ".tgz" { + switch fext { + case ".bin", ".zip", ".tar.bz", ".tar.bz2", ".bz2", ".gz", ".tar.gz", ".tgz": + // valid + default: log.Printf("fetched asset has unsupported file type: %s (ext '%s')", ga.Name, fext) continue } diff --git a/handler/search.go b/handler/search.go index ecdce4e..e351cb4 100644 --- a/handler/search.go +++ b/handler/search.go @@ -1,8 +1,8 @@ package handler import ( + "errors" "fmt" - "log" "net/http" "net/url" "regexp" @@ -10,18 +10,30 @@ import ( var searchGithubRe = regexp.MustCompile(`https:\/\/github\.com\/(\w+)\/(\w+)`) -//uses im feeling lucky and grabs the "Location" -//header from the 302, which contains the github repo -func searchGoogle(phrase string) (user, project string, err error) { +func imFeelingLuck(phrase string) (user, project string, err error) { phrase += " site:github.com" - log.Printf("google search for '%s'", phrase) + // try dgg v := url.Values{} + v.Set("q", "! " /*I'm feeling lucky*/ +phrase) + if user, project, err := captureRepoLocation(("https://html.duckduckgo.com/html?" + v.Encode())); err == nil { + return user, project, nil + } + // try google + v = url.Values{} v.Set("btnI", "") //I'm feeling lucky v.Set("q", phrase) - urlstr := "https://www.google.com/search?" + v.Encode() - req, err := http.NewRequest("GET", urlstr, nil) + if user, project, err := captureRepoLocation(("https://www.google.com/search?" + v.Encode())); err == nil { + return user, project, nil + } + return "", "", errors.New("not found") +} + +// uses im feeling lucky and grabs the "Location" +// header from the 302, which contains the github repo +func captureRepoLocation(url string) (user, project string, err error) { + req, err := http.NewRequest("GET", url, nil) if err != nil { - return "", "", err + panic(err) } req.Header.Set("Accept", "*/*") //I'm a browser... :) @@ -33,7 +45,7 @@ func searchGoogle(phrase string) (user, project string, err error) { } resp.Body.Close() //assume redirection - if resp.StatusCode != 302 { + if resp.StatusCode/100 != 3 { return "", "", fmt.Errorf("non-redirect response: %d", resp.StatusCode) } //extract Location header URL diff --git a/handler/strings.go b/handler/strings.go index d9e4091..cfd881c 100644 --- a/handler/strings.go +++ b/handler/strings.go @@ -7,7 +7,7 @@ import ( var ( archRe = regexp.MustCompile(`(arm64|arm|386|amd64|x86_64|aarch64|32|64)`) - fileExtRe = regexp.MustCompile(`(\.[a-z][a-z0-9]+)+$`) + fileExtRe = regexp.MustCompile(`(\.tar)?(\.[a-z][a-z0-9]+)$`) posixOSRe = regexp.MustCompile(`(darwin|linux|(net|free|open)bsd|mac|osx|windows|win)`) checksumRe = regexp.MustCompile(`(checksums|sha256sums)`) ) diff --git a/handler/strings_test.go b/handler/strings_test.go new file mode 100644 index 0000000..17c7e65 --- /dev/null +++ b/handler/strings_test.go @@ -0,0 +1,22 @@ +package handler + +import "testing" + +func TestFilExt(t *testing.T) { + tests := []struct { + file, ext string + }{ + {"my.file.tar.gz", ".tar.gz"}, + {"my.file.tar.bz2", ".tar.bz2"}, + {"my.file.tar.bz", ".tar.bz"}, + {"my.file.bz2", ".bz2"}, + {"my.file.gz", ".gz"}, + {"my.file.tar.zip", ".tar.zip"}, // :( + } + for _, tc := range tests { + ext := getFileExt(tc.file) + if ext != tc.ext { + t.Fatalf("getFileExt(%s) = %s, want %s", tc.file, ext, tc.ext) + } + } +} diff --git a/scripts/install.sh.tmpl b/scripts/install.sh.tmpl index 7a85cb6..5a7101d 100644 --- a/scripts/install.sh.tmpl +++ b/scripts/install.sh.tmpl @@ -100,8 +100,8 @@ function install { echo -n " as $ASPROG" fi echo -n " (${OS}/${ARCH})" - {{ if .Google }} - #matched using google, give time to cancel + {{ if .Search }} + # web search, give time to cancel echo -n " in 5 seconds" for i in 1 2 3 4 5; do sleep 1 @@ -116,6 +116,9 @@ function install { if [[ $FTYPE = ".gz" ]]; then which gzip > /dev/null || fail "gzip is not installed" bash -c "$GET $URL" | gzip -d - > $PROG || fail "download failed" + elif [[ $FTYPE = ".bz2" ]]; then + which bzip2 > /dev/null || fail "bzip2 is not installed" + bash -c "$GET $URL" | bzip2 -d - > $PROG || fail "download failed" elif [[ $FTYPE = ".tar.bz" ]] || [[ $FTYPE = ".tar.bz2" ]]; then which tar > /dev/null || fail "tar is not installed" which bzip2 > /dev/null || fail "bzip2 is not installed" diff --git a/scripts/install.txt.tmpl b/scripts/install.txt.tmpl index 6c28e78..bb601f5 100644 --- a/scripts/install.txt.tmpl +++ b/scripts/install.txt.tmpl @@ -5,7 +5,7 @@ as: {{ .AsProgram }}{{end}} release: {{ .Release }} move-into-path: {{ .MoveToPath }} sudo-move: {{ .SudoMove }} -used-google: {{ .Google }} +used-search: {{ .Search }} release assets: {{ range .Assets }} {{ .Key }}