A fast, local-first "redirection engine" for !bang users with a few extra features ^-^
1import { bangs } from "./bang"; 2import "./global.css"; 3 4function noSearchDefaultPageRender() { 5 const app = document.querySelector<HTMLDivElement>("#app"); 6 if (!app) throw new Error("App element not found"); 7 app.innerHTML = ` 8 <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh;"> 9 <div class="content-container"> 10 <h1>┐( ˘_˘ )┌</h1> 11 <p>DuckDuckGo's bang redirects are too slow. Add the following URL as a custom search engine to your browser. Enables <a href="https://duckduckgo.com/bang.html" target="_blank">all of DuckDuckGo's bangs.</a></p> 12 <div class="url-container"> 13 <input 14 type="text" 15 class="url-input" 16 value="https://unduck.link?q=%s" 17 readonly 18 /> 19 <button class="copy-button"> 20 <img src="/clipboard.svg" alt="Copy" /> 21 </button> 22 </div> 23 </div> 24 <footer class="footer"> 25 made with ♥ by <a href="https://github.com/taciturnaxolotl" target="_blank">Kieran Klukas</a> as <a href="https://github.com/taciturnaxolotl/unduck" target="_blank">open source</a> software 26 </footer> 27 </div> 28 `; 29 30 const copyButton = app.querySelector<HTMLButtonElement>(".copy-button"); 31 if (!copyButton) throw new Error("Copy button not found"); 32 const copyIcon = copyButton.querySelector("img"); 33 if (!copyIcon) throw new Error("Copy icon not found"); 34 const urlInput = app.querySelector<HTMLInputElement>(".url-input"); 35 if (!urlInput) throw new Error("URL input not found"); 36 37 urlInput.value = `${window.location.protocol}//${window.location.host}?q=%s`; 38 39 copyButton.addEventListener("click", async () => { 40 await navigator.clipboard.writeText(urlInput.value); 41 copyIcon.src = "/clipboard-check.svg"; 42 43 setTimeout(() => { 44 copyIcon.src = "/clipboard.svg"; 45 }, 2000); 46 }); 47} 48 49const LS_DEFAULT_BANG = localStorage.getItem("default-bang") ?? "g"; 50const defaultBang = bangs.find((b) => b.t === LS_DEFAULT_BANG); 51 52function getBangredirectUrl() { 53 const url = new URL(window.location.href); 54 const query = url.searchParams.get("q")?.trim() ?? ""; 55 if (!query) { 56 noSearchDefaultPageRender(); 57 return null; 58 } 59 60 const match = query.match(/!(\S+)/i); 61 62 const bangCandidate = match?.[1]?.toLowerCase(); 63 const selectedBang = bangs.find((b) => b.t === bangCandidate) ?? defaultBang; 64 65 // Remove the first bang from the query 66 const cleanQuery = query.replace(/!\S+\s*/i, "").trim(); 67 68 // Format of the url is: 69 // https://www.google.com/search?q={{{s}}} 70 const searchUrl = selectedBang?.u.replace( 71 "{{{s}}}", 72 // Replace %2F with / to fix formats like "!ghr+t3dotgg/unduck" 73 encodeURIComponent(cleanQuery).replace(/%2F/g, "/"), 74 ); 75 if (!searchUrl) return null; 76 77 return searchUrl; 78} 79 80function doRedirect() { 81 const searchUrl = getBangredirectUrl(); 82 if (!searchUrl) return; 83 window.location.replace(searchUrl); 84} 85 86doRedirect();