Skip to content

Instantly share code, notes, and snippets.

@ishideo
Forked from choffee/ddg.bb
Created April 12, 2026 14:36
Show Gist options
  • Select an option

  • Save ishideo/9aada98243c91beed58d9bda03c7b3cf to your computer and use it in GitHub Desktop.

Select an option

Save ishideo/9aada98243c91beed58d9bda03c7b3cf 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*)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment