| 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
|
| }
|