A fast, local-first "redirection engine" for !bang users with a few extra features ^-^
1import { bangs } from "./bang";
2import "./global.css";
3
4const defaultBang = bangs.find((b) => b.t === "g");
5
6function noSearchDefaultPageRender() {
7 const app = document.querySelector<HTMLDivElement>("#app")!;
8 app.innerHTML = `
9 <div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh;">
10 <div class="content-container">
11 <h1>Unduck</h1>
12 <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>
13 <div class="url-container">
14 <input
15 type="text"
16 class="url-input"
17 value="https://unduck.link?q=%s"
18 readonly
19 />
20 <button class="copy-button">
21 <img src="/clipboard.svg" alt="Copy" />
22 </button>
23 </div>
24 </div>
25 <footer class="footer">
26 created by <a href="https://x.com/theo" target="_blank">theo</a> with help from <a href="https://t3.chat" target="_blank">t3.chat</a>
27 </footer>
28 </div>
29 `;
30
31 const copyButton = app.querySelector<HTMLButtonElement>(".copy-button")!;
32 const copyIcon = copyButton.querySelector("img")!;
33 const urlInput = app.querySelector<HTMLInputElement>(".url-input")!;
34
35 copyButton.addEventListener("click", async () => {
36 await navigator.clipboard.writeText(urlInput.value);
37 copyIcon.src = "/clipboard-check.svg";
38
39 setTimeout(() => {
40 copyIcon.src = "/clipboard.svg";
41 }, 2000);
42 });
43}
44
45function getBangredirectUrl() {
46 const url = new URL(window.location.href);
47 const query = url.searchParams.get("q")?.trim() ?? "";
48 if (!query) {
49 noSearchDefaultPageRender();
50 return null;
51 }
52
53 const match = query.match(/!([a-z0-9]+)/i);
54
55 const bangCandidate = match?.[1]?.toLowerCase();
56 const selectedBang = bangs.find((b) => b.t === bangCandidate) ?? defaultBang;
57
58 // Remove the first bang from the query
59 const cleanQuery = query.replace(/![a-z0-9]+\s*/i, "").trim();
60
61 // Format of the url is:
62 // https://www.google.com/search?q={{{s}}}
63 const searchUrl = selectedBang?.u.replace(
64 "{{{s}}}",
65 encodeURIComponent(cleanQuery)
66 );
67 if (!searchUrl) return null;
68
69 return searchUrl;
70}
71
72function doRedirect() {
73 const searchUrl = getBangredirectUrl();
74 if (!searchUrl) return;
75 window.location.replace(searchUrl);
76}
77
78doRedirect();