Skip to content

Commit

Permalink
Fix DuckDuckGo web search (#147)
Browse files Browse the repository at this point in the history
duck-duck-scrape is unmaintained and doesn't work anymore.

We use the duckduckgo-search Python package instead. It is added to
requirements.txt as an optional dependency.
  • Loading branch information
XInTheDark authored Nov 15, 2024
1 parent be27724 commit ad41eef
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 93 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ installation from source is extremely simple.
clone the repository.
2. Navigate to the directory, and open a Terminal window at the downloaded folder.
3. Run `npm ci --production` to install required dependencies.
4. Run `npm run dev` to build and import the extension.
4. (Optional) Run `pip install -r requirements.txt` to install Python dependencies. These are required for
some features, e.g. web search.
5. Run `npm run dev` to build and import the extension.

The extension, and its full set of commands, should then show up in your Raycast app.

Expand Down
106 changes: 21 additions & 85 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,6 @@
],
"dependencies": {
"@raycast/api": "^1.80.0",
"duck-duck-scrape": "^2.2.5",
"fetch-to-curl": "^0.6.0",
"g4f-image": "^1.3.3",
"gemini-g4f": "^12.3.10-beta.1",
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
duckduckgo-search>=6.3.5
40 changes: 40 additions & 0 deletions src/api/tools/ddgs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Similar to the CURL interface, the DDGS interface is used to communicate
// with the `ddgs` CLI to perform DuckDuckGo searches.
// REQUIREMENT: `pip install duckduckgo-search` in order to install the `ddgs` CLI.

import { exec } from "child_process";
import { DEFAULT_ENV } from "#root/src/helpers/env.js";
import { escapeString } from "#root/src/helpers/helper.js";
import { getSupportPath } from "#root/src/helpers/extension_helper.js";
import fs from "fs";

// Return an array of the search results.
// Each result is of the form: {title, href, body}
export async function ddgsRequest(query, { maxResults = 15 } = {}) {
query = escapeString(query);

const ddgs_cmd = `ddgs text -k '${query}' -s off -m ${maxResults} -o "ddgs_results.json"`;
const cwd = getSupportPath();

const childProcess = exec(ddgs_cmd, {
env: DEFAULT_ENV,
cwd: cwd,
});

const exitCode = await new Promise((resolve, reject) => {
childProcess.on("exit", () => resolve(0));
childProcess.on("error", (err) => reject(err));
});

if (exitCode !== 0) {
throw new Error(`ddgs exited with code ${exitCode}`);
}

let results = fs.readFileSync(`${cwd}/ddgs_results.json`, "utf8");
results = JSON.parse(results);

// clean up the file
fs.writeFileSync(`${cwd}/ddgs_results.json`, "");

return results;
}
10 changes: 4 additions & 6 deletions src/api/tools/web.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Toast, showToast } from "@raycast/api";
import * as providers from "../providers.js";

import * as DDG from "duck-duck-scrape";
import { ddgsRequest } from "#root/src/api/tools/ddgs.js";
import { Preferences } from "../preferences.js";

export const webToken = "<|web_search|>",
Expand Down Expand Up @@ -68,10 +68,8 @@ export const getWebResult = async (query) => {
await showToast(Toast.Style.Animated, "Searching the web");

try {
const searchResults = await DDG.search(query, {
safeSearch: DDG.SafeSearchType.OFF,
});
return processWebResults(searchResults.results);
const results = await ddgsRequest(query);
return processWebResults(results);
} catch (e) {
await showToast(Toast.Style.Failure, "Web search failed");
return "No results found.";
Expand All @@ -88,7 +86,7 @@ export const processWebResults = (results, maxResults = 15) => {
let answer = "";
for (let i = 0; i < Math.min(results.length, maxResults); i++) {
let x = results[i];
let rst = `${i + 1}.\nURL: ${x["url"]}\nTitle: ${x["title"]}\nContent: ${x["description"]}\n\n`;
let rst = `${i + 1}.\nURL: ${x["href"]}\nTitle: ${x["title"]}\nContent: ${x["body"]}\n\n`;
answer += rst;
}
return answer;
Expand Down
7 changes: 7 additions & 0 deletions src/helpers/env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const DEFAULT_PATH =
"/opt/homebrew/opt/llvm/bin:/opt/local/bin:/opt/local/sbin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Library/Apple/usr/bin";

export const DEFAULT_ENV = {
...process.env,
PATH: DEFAULT_PATH,
};
9 changes: 9 additions & 0 deletions src/helpers/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,12 @@ export const current_datetime = () => {
second: "2-digit",
});
};

export const escapeString = (str) => {
// https://github.com/leoek/fetch-to-curl/blob/092db367955f80280d458c1e5af144e9b0f42114/src/main.js#L89C10-L89C37
return str.replace(/'/g, `'\\''`);
};

export const escapeObject = (obj) => {
return escapeString(JSON.stringify(obj));
};

0 comments on commit ad41eef

Please sign in to comment.