Skip to content

Commit

Permalink
Merge branch 'main' into proxy_support
Browse files Browse the repository at this point in the history
  • Loading branch information
XInTheDark committed Nov 18, 2024
2 parents 8fb2f2a + b44d2cc commit 96fe70d
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 85 deletions.
17 changes: 2 additions & 15 deletions src/api/curl.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,11 @@
// It may throw an error if the request fails (curl exits with a non-zero status code).
// 4. REQUIREMENT: The `curl` command must be available in the system PATH.

import { exec } from "child_process";
import { execShell } from "#root/src/api/shell.js";
import { fetchToCurl } from "fetch-to-curl";

export async function* curlRequest(url, options) {
const curl_cmd = fetchToCurl(url, options) + " --silent --no-buffer";

const childProcess = exec(curl_cmd);

for await (const chunk of childProcess.stdout) {
yield chunk.toString().replace(/\r/g, "\n");
}

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

if (exitCode !== 0) {
throw new Error(`curl exited with code ${exitCode}`);
}
yield* execShell(curl_cmd);
}
38 changes: 38 additions & 0 deletions src/api/shell.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/// This is the shell interface, which uses the command-line shell to execute commands.
/// It provides two functions: `execShell` and `execShellNoStream`.
/// `execShell` takes a command, along with other options, and returns an async generator that yields the output.
/// `execShellNoStream` is an async function that executes the command and returns the output as a string.
/// Both functions may throw an error if the command fails, unless `ignoreErrors` is set to true.

import { exec } from "child_process";
import { DEFAULT_ENV } from "#root/src/helpers/env.js";

export const DEFAULT_SHELL_OPTIONS = {
env: DEFAULT_ENV,
};

export async function* execShell(cmd, options = DEFAULT_SHELL_OPTIONS, exec_options = { ignoreErrors: false }) {
const childProcess = exec(cmd, options);

for await (const chunk of childProcess.stdout) {
yield chunk.toString().replace(/\r/g, "\n");
}

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

if (exitCode !== 0 && !exec_options.ignoreErrors) {
throw new Error(`exited with code ${exitCode}`);
}
}

export async function execShellNoStream(cmd, options = { env: DEFAULT_ENV }, exec_options = { ignoreErrors: false }) {
let output = "";
for await (const chunk of execShell(cmd, options, exec_options)) {
output += chunk;
}
output = output.trim();
return output;
}
17 changes: 2 additions & 15 deletions src/api/tools/ddgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
// 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 { DEFAULT_SHELL_OPTIONS, execShellNoStream } from "#root/src/api/shell.js";
import { escapeString } from "#root/src/helpers/helper.js";
import { getSupportPath } from "#root/src/helpers/extension_helper.js";
import fs from "fs";
Expand All @@ -16,19 +15,7 @@ export async function ddgsRequest(query, { maxResults = 15 } = {}) {
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}`);
}
await execShellNoStream(ddgs_cmd, { ...DEFAULT_SHELL_OPTIONS, cwd });

let results = fs.readFileSync(`${cwd}/ddgs_results.json`, "utf8");
results = JSON.parse(results);
Expand Down
6 changes: 2 additions & 4 deletions src/askAboutScreenContent.jsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { closeMainWindow, launchCommand, LaunchType } from "@raycast/api";
import util from "util";
import { exec } from "child_process";
import { execShellNoStream } from "#root/src/api/shell.js";
import { getAssetsPath } from "./helpers/extension_helper.js";
import { image_supported_provider_strings } from "./api/providers.js";

// Note how this command is a very special case: it is a "no-view" type command,
// which means it does not return any UI view, and instead calls askAI to handle the rendering.
// This is because the function is async, and async functions are only permitted in no-view commands.
export default async function AskAboutScreenContent(props) {
const execPromise = util.promisify(exec);
await closeMainWindow();
const path = `${getAssetsPath()}/screenshot.png`;
await execPromise(`/usr/sbin/screencapture "${path}"`);
await execShellNoStream(`/usr/sbin/screencapture "${path}"`);

await launchCommand({
name: "askAI",
Expand Down
127 changes: 76 additions & 51 deletions src/helpers/customCommands.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Storage } from "../api/storage.js";

import { Clipboard } from "@raycast/api";
import { Clipboard, showToast, Toast } from "@raycast/api";
import { getBrowserTab } from "./browser.jsx";
import { execShellNoStream } from "#root/src/api/shell.js";

export class CustomCommand {
constructor({ name = "", prompt = "", id = Date.now().toString(), options = {} }) {
Expand All @@ -13,8 +14,11 @@ export class CustomCommand {

async processPrompt(prompt, query, selected) {
this.input = query ? query : selected ? selected : "";
const regex = /{([^}]*)}/;

// const regex = /{([^}]*)}/; // left-to-right, legacy matching method
const regex = /\{([^{}]*)}/; // depth-first matching method; inner placeholders are processed first
let match;

while ((match = regex.exec(prompt))) {
const placeholder = match[1];
const processed_placeholder = await this.process_placeholder(placeholder);
Expand All @@ -37,61 +41,82 @@ export class CustomCommand {
const main = parts[0].trim();
let processed;

switch (main) {
case "input" || "selection":
processed = this.input;
break;
case "clipboard":
processed = (await Clipboard.read()).text;
break;
case "date":
// e.g. 1 June 2022
processed = new Date().toLocaleDateString([], { year: "numeric", month: "long", day: "numeric" });
break;
case "time":
// e.g. 6:45 pm
processed = new Date().toLocaleTimeString([], { hour: "numeric", minute: "numeric" });
break;
case "datetime":
// e.g. 1 June 2022 at 6:45 pm
processed = `${new Date().toLocaleDateString([], {
year: "numeric",
month: "long",
day: "numeric",
})} at ${new Date().toLocaleTimeString([], { hour: "numeric", minute: "numeric" })}`;
break;
case "day":
// e.g. Monday
processed = new Date().toLocaleDateString([], { weekday: "long" });
break;
case "browser-tab":
processed = await getBrowserTab();
break;
default:
processed = t;
break;
}

// modifiers
const modifiers = parts.slice(1).map((x) => x.trim());
for (const mod of modifiers) {
switch (mod) {
case "uppercase":
processed = processed.toUpperCase();
try {
switch (main) {
case "input" || "selection":
processed = this.input;
break;
case "clipboard":
processed = (await Clipboard.read()).text;
break;
case "date":
// e.g. 1 June 2022
processed = new Date().toLocaleDateString([], { year: "numeric", month: "long", day: "numeric" });
break;
case "time":
// e.g. 6:45 pm
processed = new Date().toLocaleTimeString([], { hour: "numeric", minute: "numeric" });
break;
case "lowercase":
processed = processed.toLowerCase();
case "datetime":
// e.g. 1 June 2022 at 6:45 pm
processed = `${new Date().toLocaleDateString([], {
year: "numeric",
month: "long",
day: "numeric",
})} at ${new Date().toLocaleTimeString([], { hour: "numeric", minute: "numeric" })}`;
break;
case "trim":
processed = processed.trim();
case "day":
// e.g. Monday
processed = new Date().toLocaleDateString([], { weekday: "long" });
break;
case "percent-encode":
processed = encodeURIComponent(processed);
case "browser-tab":
processed = await getBrowserTab();
break;
case "json-stringify":
processed = JSON.stringify(processed);
case "shell": {
const toast = await showToast(Toast.Style.Animated, "Running shell command");

const command = parts.slice(1).join("|").trim();
processed = await execShellNoStream(command);
console.log(command + "\n" + processed);

await toast.hide();
break;
}
default:
processed = t;
break;
}
} catch (e) {
console.log(e);
processed = t;
}

// modifiers are applied after the main processing, except for shell commands
if (["shell"].includes(main)) return processed;

try {
const modifiers = parts.slice(1).map((x) => x.trim());
for (const mod of modifiers) {
switch (mod) {
case "uppercase":
processed = processed.toUpperCase();
break;
case "lowercase":
processed = processed.toLowerCase();
break;
case "trim":
processed = processed.trim();
break;
case "percent-encode":
processed = encodeURIComponent(processed);
break;
case "json-stringify":
processed = JSON.stringify(processed);
break;
}
}
} catch (e) {
console.log(e);
}

return processed;
Expand Down

0 comments on commit 96fe70d

Please sign in to comment.