package ai import ( "encoding/json" "fmt" "io" "mbot/misc" "net/http" "net/url" "strings" "github.com/PuerkitoBio/goquery" "github.com/fatih/color" "github.com/sashabaranov/go-openai" "github.com/sashabaranov/go-openai/jsonschema" ) // getSearchOnionDefinition returns an OpenAI function definition // for searching the Dark Web (onion sites). func getSearchOnionDefinition() openai.FunctionDefinition { return openai.FunctionDefinition{ Name: "search_onion", Description: "Performs a search on the dark web and summarizes the results based on a user's query.", Parameters: jsonschema.Definition{ Type: jsonschema.Object, Properties: map[string]jsonschema.Definition{ "query": { Type: jsonschema.String, Description: "The search query string.", }, }, Required: []string{"query"}, }, } } // searchOnionHandler is the function that OpenAI will call // to handle "search_onion" requests using function calling. func searchOnionHandler(arguments json.RawMessage) (string, error) { var params struct { Query string `json:"query"` } if err := json.Unmarshal(arguments, ¶ms); err != nil { return "", fmt.Errorf("invalid arguments: %v", err) } if params.Query == "" { return "", fmt.Errorf("query parameter is required") } color.Cyan(">> Performing Onion/Dark Web search for: %s", params.Query) refinedQuery := params.Query searchResults, err := searchOnion(refinedQuery) if err != nil { return "", fmt.Errorf("error performing dark web search: %v", err) } if len(searchResults) == 0 { return "No results found on the dark web.", nil } var summaries []string for i, result := range searchResults { if i >= 3 { break } content, err := fetchOnionContent(result.Link, 20000) if err != nil { color.Red("Error fetching content from %s: %v", result.Link, err) continue } summary, err := summarizeContent(content, params.Query) if err != nil { color.Red("Error summarizing content from %s: %v", result.Link, err) continue } summaries = append(summaries, fmt.Sprintf("**%s**\n%s", result.Title, summary)) } if len(summaries) == 0 { return "Unable to fetch or summarize any of the dark web results.", nil } finalSummary, err := summarizeSummaries(summaries, params.Query) if err != nil { return "", fmt.Errorf("error summarizing results: %v", err) } return finalSummary, nil } func searchOnion(query string) ([]struct{ Title, Link string }, error) { onionSearchURL := fmt.Sprintf("https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/html?q=%s", url.QueryEscape(query)) client, err := misc.NewTorHTTPClient() if err != nil { return nil, fmt.Errorf("failed to create Tor client: %v", err) } req, err := http.NewRequest("GET", onionSearchURL, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %v", err) } req.Header.Set("User-Agent", "Mozilla/5.0") resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("failed to perform onion request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("onion search returned status code %d", resp.StatusCode) } doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { return nil, fmt.Errorf("failed to parse onion search results: %v", err) } var results []struct{ Title, Link string } doc.Find("a.result__a").Each(func(i int, s *goquery.Selection) { title := strings.TrimSpace(s.Text()) link, _ := s.Attr("href") if title != "" && link != "" { if !strings.HasPrefix(link, "http") { link = "https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion" + link } results = append(results, struct{ Title, Link string }{ Title: title, Link: link, }) } }) return results, nil } func fetchOnionContent(websiteURL string, maxChars int) (string, error) { resolvedURL, err := resolveRedirect(websiteURL) if err != nil { return "", fmt.Errorf("error resolving redirect: %v", err) } color.Yellow(">> Fetching content from onion URL: %s", resolvedURL) client, err := misc.NewTorHTTPClient() if err != nil { return "", fmt.Errorf("failed to create Tor client: %v", err) } req, err := http.NewRequest("GET", resolvedURL, nil) if err != nil { return "", fmt.Errorf("failed to create HTTP request: %v", err) } req.Header.Set("User-Agent", "Mozilla/5.0") resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to perform HTTP request over Tor: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("received non-OK HTTP status: %s", resp.Status) } bodyBytes, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("failed to read response body: %v", err) } doc, err := goquery.NewDocumentFromReader(strings.NewReader(string(bodyBytes))) if err != nil { return "", fmt.Errorf("failed to parse HTML: %v", err) } textContent := doc.Find("body").Text() textContent = strings.TrimSpace(textContent) textContent = strings.ReplaceAll(textContent, "\n", " ") textContent = strings.Join(strings.Fields(textContent), " ") if len(textContent) > maxChars { textContent = textContent[:maxChars] + "..." } return textContent, nil } func resolveRedirect(link string) (string, error) { client, err := misc.NewTorHTTPClient() if err != nil { return "", fmt.Errorf("failed to create Tor client: %v", err) } req, err := http.NewRequest("GET", link, nil) if err != nil { return "", fmt.Errorf("failed to create HTTP request: %v", err) } req.Header.Set("User-Agent", "Mozilla/5.0") resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("failed to resolve redirect: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("non-OK HTTP status: %d", resp.StatusCode) } return resp.Request.URL.String(), nil }