Skip to content

Commit 6673bcf

Browse files
authored
feat(search): add web search command (#1091)
2 parents def82d7 + 5371500 commit 6673bcf

File tree

5 files changed

+118
-1
lines changed

5 files changed

+118
-1
lines changed

locales/en-US/info.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ channelCreated: "I've created the %channel% channel."
1313
channelDeleted: "I've deleted the **%channel%** channel."
1414
channelUpdated: "I've updated the %channel% channel."
1515
comicNotFound: "I didn't find any comic. Please try again."
16+
commndMissingRequirements: "This command is not yet configured to run. Please contact the bot owner to set it up."
1617
commandThreadOnly: "This command can only be used in a thread."
1718
diceNotationInvalid: "`%notation%` is an invalid (or unsupported) dice notation."
1819
rewardsClaimed: "You've claimed your daily reward of **%amount% Bastion Coins**."

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "bastion",
3-
"version": "10.16.2",
3+
"version": "10.17.0",
44
"description": "Get an enhanced Discord experience!",
55
"type": "module",
66
"homepage": "https://bastion.traction.one",

settings.example.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ anthropic:
109109
# Change the `maxTokens` value to set the length of Claude's responses.
110110
# https://docs.anthropic.com/en/docs/about-claude/models#model-comparison-table
111111
maxTokens: 128
112+
# Optionally required for `search` command.
113+
# For more details, check https://www.tavily.com/#pricing
114+
tavilyApiKey: ""
112115
# Required for `weather` command.
113116
openWeatherMapApiKey: ""
114117
# Required for `movie` and `tv` commands.

src/commands/search/web.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*!
2+
* @author TRACTION (iamtraction)
3+
* @copyright 2025
4+
*/
5+
import { ApplicationCommandOptionType, ChatInputCommandInteraction } from "discord.js";
6+
import { Client, Command } from "@bastion/tesseract";
7+
8+
import * as requests from "../../utils/requests.js";
9+
import Settings from "../../utils/settings.js";
10+
11+
declare interface TavilyResponse {
12+
query: string;
13+
answer: string;
14+
images: {
15+
url: string;
16+
description?: string;
17+
}[];
18+
results: {
19+
title: string;
20+
url: string;
21+
content: string;
22+
score: number;
23+
raw_content: string | null;
24+
favicon: string;
25+
}[];
26+
auto_parameters: {
27+
topic: "general" | "news" | "finance";
28+
search_depth: string;
29+
};
30+
response_time: number;
31+
request_id: string;
32+
}
33+
34+
class WebCommand extends Command {
35+
constructor() {
36+
super({
37+
name: "web",
38+
description: "Searches the web for the specified query.",
39+
options: [
40+
{
41+
type: ApplicationCommandOptionType.String,
42+
name: "query",
43+
description: "The query you want to search for.",
44+
required: true,
45+
},
46+
{
47+
type: ApplicationCommandOptionType.String,
48+
name: "freshness",
49+
description: "Specifies the time range for search results.",
50+
choices: [
51+
{ name: "Past day", value: "d" },
52+
{ name: "Past week", value: "w" },
53+
{ name: "Past month", value: "m" },
54+
{ name: "Past year", value: "y" },
55+
],
56+
},
57+
{
58+
type: ApplicationCommandOptionType.String,
59+
name: "topic",
60+
description: "Specifies the category of the search.",
61+
choices: [
62+
{ name: "General", value: "general" },
63+
{ name: "News", value: "news" },
64+
{ name: "Finance", value: "finance" },
65+
],
66+
},
67+
],
68+
});
69+
}
70+
71+
public async exec(interaction: ChatInputCommandInteraction<"cached">): Promise<unknown> {
72+
await interaction.deferReply();
73+
const query = interaction.options.getString("query");
74+
const freshness = interaction.options.getString("freshness") || undefined;
75+
const topic = interaction.options.getString("topic") || undefined;
76+
77+
const apiKey = ((interaction.client as Client).settings as Settings)?.get("tavilyApiKey");
78+
79+
if (!apiKey) {
80+
return await interaction.reply({
81+
content: (interaction.client as Client).locales.getText(interaction.guildLocale, "commndMissingRequirements"),
82+
ephemeral: true,
83+
});
84+
}
85+
86+
// fetch web search results
87+
const { body } = await requests.post(
88+
"https://api.tavily.com/search",
89+
{
90+
"authorization": `Bearer ${ ((interaction.client as Client).settings as Settings)?.get("tavilyApiKey") }`,
91+
},
92+
null,
93+
{
94+
query,
95+
include_answer: true,
96+
freshness,
97+
topic,
98+
},
99+
);
100+
101+
const data = await body.json() as TavilyResponse;
102+
103+
await interaction.editReply({
104+
content: `${ data.answer }
105+
106+
-# **${ data.results.length } Sources** in \`${ data.response_time }s\`
107+
${ data.results.map(result => `- -# [**${ result.title }**](<${ result.url }>)`).join("\n") }`,
108+
});
109+
}
110+
}
111+
112+
export { WebCommand as Command };

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export namespace bastion {
2727
model?: string;
2828
maxTokens?: number;
2929
};
30+
tavilyApiKey?: string;
3031
openWeatherMapApiKey?: string;
3132
tmdbApiKey?: string;
3233
trackerNetworkApiKey?: string;

0 commit comments

Comments
 (0)