Current section
Files
Jump to
Current section
Files
lib/sear_ex_ng.ex
defmodule SearExNg do
@moduledoc """
A client library for interacting with the SearxNG search API.
"""
@default_base_url "https://searx.perennialte.ch"
@default_timeout 10_000 # 10 seconds
defstruct [:base_url, :timeout]
@doc """
Creates a new SearExNg client configuration.
## Parameters
- opts: Keyword list of options
- `:base_url` - The SearxNG instance URL (default: #{@default_base_url})
- `:timeout` - Request timeout in milliseconds (default: #{@default_timeout})
## Examples
iex> SearExNg.new(base_url: "https://my-searxng-instance.com")
%SearExNg{base_url: "https://my-searxng-instance.com", timeout: 10000}
"""
def new(opts \\ []) do
%__MODULE__{
base_url: Keyword.get(opts, :base_url, @default_base_url),
timeout: Keyword.get(opts, :timeout, @default_timeout)
}
end
@doc """
Performs a search query against the SearxNG instance.
## Parameters
- client: The SearExNg client struct
- query: The search query string
- opts: Keyword list of search options
- `:categories` - List of categories to search in
- `:engines` - List of engines to use
- `:format` - Response format ("json", "rss") (default: "json")
- `:page` - Page number (default: 1)
- `:language` - Language code (e.g., "en-US")
- `:time_range` - Time range for results ("day", "week", "month", "year")
## Examples
iex> client = SearExNg.new()
iex> SearExNg.search(client, "site:x.com cybersecurity news", time_range: "day")
{:ok, %{"results" => [...]}}
"""
def search(client, query, opts \\ []) when is_binary(query) do
form_data = build_search_params(query, opts)
format = Keyword.get(opts, :format, "json")
request_options = [
base_url: client.base_url,
form: form_data,
receive_timeout: client.timeout,
decode_body: false
]
with {:ok, response} <- Req.post("/search", request_options),
{:ok, body} <- decode_response(response.body, format) do
{:ok, body}
else
{:error, reason} -> {:error, reason}
end
end
defp build_search_params(query, opts) do
[]
|> Keyword.put(:q, query)
|> add_param(:categories, opts)
|> add_param(:engines, opts)
|> add_param(:format, opts)
|> add_param(:pageno, opts, :page)
|> add_param(:lang, opts, :language)
|> add_param(:time_range, opts)
end
# Special case for :format with default value
defp add_param(params, :format, opts) do
format = Keyword.get(opts, :format, "json")
Keyword.put(params, :format, format)
end
defp add_param(params, key, opts, opt_key \\ nil) do
opt_key = opt_key || key
case Keyword.get(opts, opt_key) do
nil -> params
value when is_list(value) -> Keyword.put(params, key, Enum.join(value, ","))
value -> Keyword.put(params, key, value)
end
end
defp decode_response(body, "json") do
Jason.decode(body)
end
defp decode_response(_body, "rss") do
IO.puts("rss not yet implemented")
{:ok, "rss not yet implemented"}
end
defp decode_response(_body, format) do
{:error, "Unsupported format: #{format}"}
end
end