Skip to content

Instantly share code, notes, and snippets.

@choffee
Last active April 16, 2024 10:14
Show Gist options
  • Save choffee/a56effb8aab913c4680658187f0fead7 to your computer and use it in GitHub Desktop.
Save choffee/a56effb8aab913c4680658187f0fead7 to your computer and use it in GitHub Desktop.
Cli tool for selecting DuckDuckGo bang searches unsing Babashka and gum.
#!/usr/bin/env bb
;; Script to search ddg.
;; Takes a search term then asks you to select a bang.
;; Copyright John Cooper
;; This program is free software: you can redistribute it and/or modify it under the terms of the
;; GNU General Public License as published by the Free Software Foundation,
;; either version 3 of the License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
;; See the GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License along with this program. ;;
;; If not, see <https://www.gnu.org/licenses/>.
;; Ideas for how it would work.
;; ddg _dpkg foobar => !dpkg foobar
;; ddg -b dpkg foobar => !dpkg foobar
;; ddg foobar => prompt for banging search param.
;; (let [cacheurl "https://duckduckgo.com/bang.v260.js"])
;; You need to install gum from https://charm.sh
(babashka.deps/add-deps
'{:deps {io.github.lispyclouds/bblgum {:git/sha "7ebae0e2231899fe2a6ad44bc9ef5fca64099fcd"}}})
(ns bin.ddg
(:require [babashka.process :as p]
[clojure.string :as str]
[babashka.http-client :as http]
[cheshire.core :as json]
[babashka.fs :as fs]
[bblgum.core :as b]))
(def cache-file (str (fs/path (fs/xdg-cache-home) "ddg_bangs.json")))
(defn cache_valid? [filename seconds]
"Is a file older than x seconds?"
(and (fs/exists? cache-file)
(> (+ (* seconds 1000) (fs/file-time->millis (fs/last-modified-time filename)))
(System/currentTimeMillis))))
(defn get_bangs_json []
"Fetch the list of bangs from DDG
Later cache it..."
(let [cacheurl "https://duckduckgo.com/bang.js"]
(json/parse-string
(cond
(cache_valid? cache-file (* 60 60 24))
(slurp cache-file)
:else
(let [cache-data (:body (http/get cacheurl {:headers {"Accept" "application/json" "user-agent" "curl/7.87.0"} :throw false}))]
(spit cache-file cache-data)
cache-data)))))
(defn open_ddg [searchterm]
"Given a search term open DuckDuckGo in default browser"
(p/shell "xdg-open" (str "https://duckduckgo.com/?q=" searchterm)))
;; => {"c" "Tech", "d" "www.01net.com", "r" 6, "s" "01net", "sc" "Downloads (apps)", "t" "01net", "u" "http://www.01net.com/recherche/recherche.php?searchstring={{{s}}}&chaine=home"}
(defn bang->selector [bang]
"Takes in a bang json and returns a string for selecting"
(str/join ": " (list (get bang "t") (get bang "s") (get bang "sc"))))
(defn selector->bang [selector_string]
"Give the bang string from the selector"
(first (str/split selector_string #":")))
(defn bangs_json->gumlist [bangs_json]
(map bang->selector bangs_json))
(defn ask_for_bang [bangs_json]
"Ask user to select the bang for the search"
(->>
(b/gum {:cmd :filter
:in (str/join "\n" (bangs_json->gumlist bangs_json))})
:result
first
selector->bang))
(defn search_term [bang args]
(->>
(str/join " " args)
(str "!" bang " ")))
(let [bangs_json (get_bangs_json)]
(open_ddg (search_term (ask_for_bang bangs_json) *command-line-args*)))
@choffee
Copy link
Author

choffee commented Apr 16, 2024

I've updated the url of the bangs to a more generic one so it works again now and should not need updating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment