broadfield commited on
Commit
902f54b
Β·
verified Β·
1 Parent(s): 00f1392

Add web search & scrape module

Browse files
Files changed (1) hide show
  1. modules_search.js +295 -0
modules_search.js ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Browser Search & Scrape Module for HuggingBot
2
+ // Uses the Browser Search/Scrape API endpoint
3
+ // Primary: POST to https://broadfield-dev-browser.hf.space/api/web_browse
4
+
5
+ (function(MS) {
6
+ MS.register("search-web", {
7
+ name: "Web Search 2.0",
8
+ description: "Browser-based web search and page scraping using the broadfield-dev API endpoint.",
9
+ tools: [
10
+ {
11
+ type: "function",
12
+ function: {
13
+ name: "search_web",
14
+ description: "Search the internet for current information. Uses a browser-based search API to fetch live results. Returns titles, snippets, and URLs.",
15
+ parameters: {
16
+ type: "object",
17
+ properties: {
18
+ query: { type: "string", description: "Search query - be specific for best results" },
19
+ max_results: { type: "number", description: "Max results (1-10)", default: 5 },
20
+ source: { type: "string", enum: ["auto", "web", "wikipedia", "news", "reddit"], description: "Source to search.", default: "auto" }
21
+ },
22
+ required: ["query"]
23
+ }
24
+ }
25
+ },
26
+ {
27
+ type: "function",
28
+ function: {
29
+ name: "scrape_web",
30
+ description: "Scrape/extract the full content of a webpage by URL. Uses a browser-based API to fetch and return the page's rendered content (text, HTML, or structured data).",
31
+ parameters: {
32
+ type: "object",
33
+ properties: {
34
+ url: { type: "string", description: "The full URL of the webpage to scrape (e.g. https://example.com/page)" },
35
+ max_length: { type: "number", description: "Maximum length of returned content in characters (100-50000)", default: 5000 }
36
+ },
37
+ required: ["url"]
38
+ }
39
+ }
40
+ }
41
+ ],
42
+ handleToolCall: async function(tn, args) {
43
+ if (tn === "search_web") {
44
+ try {
45
+ var q = args.query;
46
+ var mx = Math.min(Math.max(parseInt(args.max_results) || 5, 1), 10);
47
+ var BROWSER_API = "https://broadfield-dev-browser-api.hf.space/web_browse";
48
+ var allResults = [];
49
+
50
+ try {
51
+ var payload = { action: "Search", query: q, browser: "webkit", search_engine: "DuckDuckGo" };
52
+ var resp = await fetch(BROWSER_API, {
53
+ method: "POST",
54
+ headers: { "Content-Type": "application/json" },
55
+ body: JSON.stringify(payload),
56
+ signal: AbortSignal.timeout(30000)
57
+ });
58
+
59
+ if (resp.ok) {
60
+ var data = await resp.json();
61
+ if (data.results && Array.isArray(data.results)) {
62
+ for (var i = 0; i < data.results.length && allResults.length < mx; i++) {
63
+ var r = data.results[i];
64
+ var title = r.title || r.name || "";
65
+ var snippet = r.snippet || r.description || r.summary || "";
66
+ var urlStr = r.url || r.link || r.href || "";
67
+ if (title) allResults.push({ title: title, snippet: snippet, url: urlStr, source: "Web" });
68
+ }
69
+ }
70
+ if (data.items && Array.isArray(data.items)) {
71
+ for (var i = 0; i < data.items.length && allResults.length < mx; i++) {
72
+ var it = data.items[i];
73
+ var title = it.title || it.name || "";
74
+ var snippet = it.snippet || it.description || it.summary || "";
75
+ var urlStr = it.link || it.url || it.href || "";
76
+ if (title) allResults.push({ title: title, snippet: snippet, url: urlStr, source: "Web" });
77
+ }
78
+ }
79
+ if (data.data && Array.isArray(data.data)) {
80
+ for (var i = 0; i < data.data.length && allResults.length < mx; i++) {
81
+ var d = data.data[i];
82
+ var title = d.title || d.name || "";
83
+ var snippet = d.snippet || d.description || d.sampleText || "";
84
+ var urlStr = d.url || d.link || d.href || "";
85
+ if (title) allResults.push({ title: title, snippet: snippet, url: urlStr, source: "Web" });
86
+ }
87
+ }
88
+ if ((data.html || data.text || data.content) && allResults.length === 0) {
89
+ var htmlContent = data.html || data.text || data.content;
90
+ if (typeof htmlContent === "string" && htmlContent.length > 50) {
91
+ var linkRegex = /<a[^>]*href=["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
92
+ var match;
93
+ while ((match = linkRegex.exec(htmlContent)) !== null && allResults.length < mx) {
94
+ var linkUrl = match[1];
95
+ var linkText = match[2].replace(/<[^>]*>/g, "").trim();
96
+ if (linkText && linkUrl && !linkUrl.startsWith("#") && !linkUrl.startsWith("javascript:")) {
97
+ if (linkUrl.startsWith("//")) linkUrl = "https:" + linkUrl;
98
+ else if (linkUrl.startsWith("/")) linkUrl = "https://google.com" + linkUrl;
99
+ else if (!linkUrl.startsWith("http")) linkUrl = "https://" + linkUrl;
100
+ var contextMatch = htmlContent.substring(Math.max(0, match.index - 150), match.index + match[0].length + 150);
101
+ var snippet = contextMatch.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim().substring(0, 200);
102
+ allResults.push({ title: linkText, snippet: snippet, url: linkUrl, source: "Web" });
103
+ }
104
+ }
105
+ }
106
+ }
107
+ if (data.success && data.output && allResults.length === 0) {
108
+ var output = typeof data.output === "string" ? data.output : JSON.stringify(data.output);
109
+ var urlExtractRegex = /(https?:\/\/[^\s"'>)+]+)/g;
110
+ var urlMatch;
111
+ var idx = 0;
112
+ while ((urlMatch = urlExtractRegex.exec(output)) !== null && allResults.length < mx) {
113
+ var foundUrl = urlMatch[1];
114
+ var before = output.substring(Math.max(0, urlMatch.index - 80), urlMatch.index);
115
+ var title = before.replace(/[|\[\]]/g, "").trim();
116
+ if (title.length > 50) title = title.substring(title.length - 50);
117
+ if (!title) title = "Result " + (idx + 1);
118
+ allResults.push({ title: title, snippet: output.substring(Math.max(0, urlMatch.index - 30), Math.min(output.length, urlMatch.index + 100)).replace(/\n/g, " ").trim(), url: foundUrl, source: "Web" });
119
+ idx++;
120
+ }
121
+ }
122
+ if (allResults.length === 0 && data) {
123
+ var textContent = typeof data === "string" ? data : JSON.stringify(data);
124
+ if (textContent && textContent.length > 20) {
125
+ allResults.push({ title: "Search Result", snippet: textContent.substring(0, 500), url: "", source: "Web" });
126
+ }
127
+ }
128
+ } else {
129
+ var errorText = await resp.text().catch(function() { return "Unknown error"; });
130
+ allResults.push({ title: "API Error", snippet: "Primary API returned status " + resp.status + ": " + errorText.substring(0, 200), url: "", source: "Web" });
131
+ }
132
+ } catch(e) {
133
+ allResults.push({ title: "Connection Error", snippet: "Could not reach the browser search API: " + e.message, url: "", source: "Web" });
134
+ }
135
+
136
+ if (allResults.length === 0 || allResults[0].title === "API Error" || allResults[0].title === "Connection Error" || allResults[0].title === "Search Result") {
137
+ try {
138
+ var ddgUrl = "https://api.duckduckgo.com/?q=" + encodeURIComponent(q) + "&format=json&no_html=1&skip_disambig=1";
139
+ var ddgResp = await fetch(ddgUrl, { method: "GET", signal: AbortSignal.timeout(10000) });
140
+ if (ddgResp.ok) {
141
+ var ddgData = await ddgResp.json();
142
+ var ddgResults = [];
143
+ if (ddgData.AbstractText) {
144
+ ddgResults.push({ title: ddgData.Headline || ddgData.Heading || "Summary", snippet: ddgData.AbstractText, url: ddgData.AbstractURL || "", source: "DuckDuckGo" });
145
+ }
146
+ var topics = ddgData.RelatedTopics || [];
147
+ for (var i = 0; i < topics.length && ddgResults.length < mx; i++) {
148
+ var topic = topics[i];
149
+ if (topic.Topics && Array.isArray(topic.Topics)) {
150
+ for (var j = 0; j < topic.Topics.length && ddgResults.length < mx; j++) {
151
+ var sub = topic.Topics[j];
152
+ if (sub.Text || sub.FirstURL) ddgResults.push({ title: sub.Text ? sub.Text.split(" - ")[0] || sub.Text : "Result", snippet: sub.Text || "", url: sub.FirstURL || "", source: "DuckDuckGo" });
153
+ }
154
+ } else if (topic.Text || topic.FirstURL) {
155
+ ddgResults.push({ title: topic.Text ? topic.Text.split(" - ")[0] || topic.Text : "Result", snippet: topic.Text || "", url: topic.FirstURL || "", source: "DuckDuckGo" });
156
+ }
157
+ }
158
+ if (ddgData.Results && Array.isArray(ddgData.Results)) {
159
+ for (var i = 0; i < ddgData.Results.length && ddgResults.length < mx; i++) {
160
+ var res = ddgData.Results[i];
161
+ if (res.Text || res.FirstURL) ddgResults.push({ title: res.Text ? res.Text.split(" - ")[0] || res.Text : "Result", snippet: res.Text || "", url: res.FirstURL || "", source: "DuckDuckGo" });
162
+ }
163
+ }
164
+ if (ddgResults.length > 0) {
165
+ allResults = ddgResults;
166
+ } else {
167
+ var htmlSearchUrl = "https://html.duckduckgo.com/html/?q=" + encodeURIComponent(q);
168
+ var htmlResp = await fetch(htmlSearchUrl, { method: "GET", signal: AbortSignal.timeout(10000) });
169
+ if (htmlResp.ok) {
170
+ var htmlText = await htmlResp.text();
171
+ var linkRegex2 = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>/gi;
172
+ var snippetRegex = /<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/gi;
173
+ var linkMatches = [], snippetMatches = [], lm, sm;
174
+ while ((lm = linkRegex2.exec(htmlText)) !== null) linkMatches.push({ url: lm[1], title: lm[2].replace(/<[^>]*>/g, "").trim() });
175
+ while ((sm = snippetRegex.exec(htmlText)) !== null) snippetMatches.push(sm[1].replace(/<[^>]*>/g, "").trim());
176
+ var htmlResults = [];
177
+ for (var i = 0; i < linkMatches.length && htmlResults.length < mx; i++) {
178
+ var lmi = linkMatches[i], snip = snippetMatches[i] || "", cleanUrl = lmi.url;
179
+ var redirectMatch = cleanUrl.match(/uddg=(https?[^&]+)/);
180
+ if (redirectMatch) cleanUrl = decodeURIComponent(redirectMatch[1]);
181
+ htmlResults.push({ title: lmi.title, snippet: snip, url: cleanUrl, source: "DuckDuckGo" });
182
+ }
183
+ if (htmlResults.length > 0) allResults = htmlResults;
184
+ }
185
+ }
186
+ }
187
+ } catch(e) {
188
+ if (allResults.length === 0 || allResults[0].title === "API Error" || allResults[0].title === "Connection Error") {
189
+ allResults = [{ title: "Search Unavailable", snippet: "Both search methods are currently unavailable. Please try again later.", url: "", source: "Web" }];
190
+ }
191
+ }
192
+ }
193
+
194
+ var seen = {}, deduped = [];
195
+ for (var i = 0; i < allResults.length && deduped.length < mx; i++) {
196
+ var item = allResults[i];
197
+ if (!item.title) continue;
198
+ var key = item.url || item.snippet || item.title;
199
+ if (key && !seen[key.substring(0, 100)]) { seen[key.substring(0, 100)] = true; deduped.push(item); }
200
+ }
201
+
202
+ var out = ["## πŸ” Search Results: \"" + q + "\"", ""];
203
+ if (deduped.length === 0) {
204
+ out.push("*No results found. Try a different query.*");
205
+ } else {
206
+ for (var i = 0; i < deduped.length; i++) {
207
+ var r = deduped[i];
208
+ out.push("### " + (i + 1) + ". " + (r.title || ""));
209
+ if (r.snippet) out.push("> " + r.snippet);
210
+ if (r.url) out.push("πŸ”— " + r.url);
211
+ out.push("");
212
+ }
213
+ }
214
+ return out.join("\n");
215
+ } catch(e) {
216
+ return "## πŸ” Search: \"" + args.query + "\"\n\n❌ Error: " + e.message;
217
+ }
218
+ }
219
+
220
+ if (tn === "scrape_web") {
221
+ try {
222
+ var urlToScrape = args.url;
223
+ var maxLen = Math.min(Math.max(parseInt(args.max_length) || 5000, 100), 50000);
224
+ var BROWSER_API = "https://broadfield-dev-browser-api.hf.space/web_browse";
225
+ var out = ["## πŸ“„ Scrape: " + urlToScrape, ""];
226
+
227
+ try {
228
+ var payload = { action: "Scrape", query: urlToScrape, browser: "webkit" };
229
+ var resp = await fetch(BROWSER_API, {
230
+ method: "POST",
231
+ headers: { "Content-Type": "application/json" },
232
+ body: JSON.stringify(payload),
233
+ signal: AbortSignal.timeout(60000)
234
+ });
235
+
236
+ if (resp.ok) {
237
+ var data = await resp.json();
238
+ var content = "";
239
+ if (data.text && typeof data.text === "string") {
240
+ content = data.text;
241
+ } else if (data.content && typeof data.content === "string") {
242
+ content = data.content;
243
+ } else if (data.html && typeof data.html === "string") {
244
+ content = data.html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "")
245
+ .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "")
246
+ .replace(/<[^>]+>/g, " ")
247
+ .replace(/&[a-z]+;/g, " ")
248
+ .replace(/\s+/g, " ")
249
+ .trim();
250
+ } else if (data.body && typeof data.body === "string") {
251
+ content = data.body.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
252
+ } else if (data.markdown && typeof data.markdown === "string") {
253
+ content = data.markdown;
254
+ } else if (data.title || data.description) {
255
+ content = "Title: " + (data.title || "") + "\nDescription: " + (data.description || "");
256
+ if (data.headings) content += "\n\nHeadings: " + (Array.isArray(data.headings) ? data.headings.join(", ") : data.headings);
257
+ } else {
258
+ var stringified = JSON.stringify(data, null, 2);
259
+ if (stringified.length > maxLen * 2) stringified = stringified.substring(0, maxLen * 2) + "\n... [truncated]";
260
+ content = stringified;
261
+ }
262
+
263
+ if (content.length > maxLen) {
264
+ content = content.substring(0, maxLen) + "\n\n... [Content truncated. Use a higher max_length to get more.]";
265
+ }
266
+
267
+ if (content && content.trim().length > 0) {
268
+ out.push("**Title:** " + (data.title || data.heading || "N/A"));
269
+ if (data.description) out.push("**Description:** " + data.description);
270
+ out.push("");
271
+ out.push("```\n" + content + "\n```");
272
+ } else {
273
+ out.push("*No readable content could be extracted from this page.*");
274
+ out.push("");
275
+ out.push("Raw response keys: " + Object.keys(data).join(", "));
276
+ }
277
+ } else {
278
+ var errorText = await resp.text().catch(function() { return "Unknown error"; });
279
+ out.push("❌ **API Error:** The scrape API returned status " + resp.status);
280
+ out.push("> " + errorText.substring(0, 500));
281
+ }
282
+ } catch(e) {
283
+ out.push("❌ **Connection Error:** Could not reach the scrape API: " + e.message);
284
+ }
285
+
286
+ return out.join("\n");
287
+ } catch(e) {
288
+ return "## πŸ“„ Scrape Error\n\n❌ Error: " + e.message;
289
+ }
290
+ }
291
+
292
+ return null;
293
+ }
294
+ });
295
+ })(ModuleSystem);