100 lines
3.9 KiB
Lua
100 lines
3.9 KiB
Lua
|
|
-- DuckDuckGo Instant Answer — short factual lookups WITHOUT an API key.
|
|||
|
|
--
|
|||
|
|
-- Endpoint: https://api.duckduckgo.com/?q=<query>&format=json&no_html=1
|
|||
|
|
--
|
|||
|
|
-- DDG returns a JSON object. We look at, in order:
|
|||
|
|
-- 1. `AbstractText` — Wikipedia-style summary, usually 1–3 sentences
|
|||
|
|
-- 2. `Answer` — direct answer for math / conversion / calc queries
|
|||
|
|
-- 3. `Definition` — dictionary definitions
|
|||
|
|
-- 4. `RelatedTopics[1].Text` — first related topic as last resort
|
|||
|
|
--
|
|||
|
|
-- If nothing useful comes back, we open the regular DDG search page so the
|
|||
|
|
-- user can read it themselves.
|
|||
|
|
|
|||
|
|
local lang = jarvis.context.language
|
|||
|
|
local raw = (jarvis.context.phrase or ""):lower()
|
|||
|
|
|
|||
|
|
local triggers = {
|
|||
|
|
"что такое", "кто такой", "кто такая",
|
|||
|
|
"расскажи про", "расскажи о",
|
|||
|
|
"найди информацию о", "найди информацию про",
|
|||
|
|
"поищи в интернете", "поищи в сети",
|
|||
|
|
"погугли что такое",
|
|||
|
|
"what is", "who is", "tell me about", "look up", "search the web for",
|
|||
|
|
}
|
|||
|
|
local query = jarvis.text.strip_trigger(raw, triggers)
|
|||
|
|
if not query or query == "" then query = raw end
|
|||
|
|
query = query:gsub("^%s+", ""):gsub("%s+$", "")
|
|||
|
|
|
|||
|
|
if query == "" then
|
|||
|
|
return jarvis.cmd.not_found(lang == "ru" and "Что искать?" or "What to look up?")
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- URL-encode (basic — DDG accepts both UTF-8 and percent-encoded)
|
|||
|
|
local function url_encode(s)
|
|||
|
|
return (s:gsub("([^%w%-_%.~])", function(c)
|
|||
|
|
return string.format("%%%02X", string.byte(c))
|
|||
|
|
end))
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local url = "https://api.duckduckgo.com/?q="
|
|||
|
|
.. url_encode(query)
|
|||
|
|
.. "&format=json&no_html=1&skip_disambig=1"
|
|||
|
|
|
|||
|
|
jarvis.log("info", "ddg query: " .. query)
|
|||
|
|
local res = jarvis.http.get(url, { ["User-Agent"] = "Mozilla/5.0 J.A.R.V.I.S." })
|
|||
|
|
|
|||
|
|
if not res or not res.ok then
|
|||
|
|
return jarvis.cmd.error(lang == "ru" and "DDG не отвечает." or "DDG didn't respond.")
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local body = res.body or ""
|
|||
|
|
|
|||
|
|
-- Helper: pull the first top-level JSON string field. Quick-and-dirty regex
|
|||
|
|
-- works because DDG never nests the fields we care about, AND because
|
|||
|
|
-- consequent `"` inside values are escaped as `\"`.
|
|||
|
|
local function field(name)
|
|||
|
|
local pat = '"' .. name .. '"%s*:%s*"((\\.[^"]*|[^"])*)"'
|
|||
|
|
local v = body:match('"' .. name .. '"%s*:%s*"(.-)"')
|
|||
|
|
if not v or v == "" then return nil end
|
|||
|
|
-- unescape common JSON sequences
|
|||
|
|
v = v:gsub('\\"', '"'):gsub('\\\\', '\\'):gsub('\\n', ' '):gsub('\\t', ' ')
|
|||
|
|
-- trim, collapse whitespace
|
|||
|
|
v = v:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
|
|||
|
|
if v == "" then return nil end
|
|||
|
|
return v
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- First related topic — array element is itself an object with "Text".
|
|||
|
|
local function first_related()
|
|||
|
|
-- Find start of "RelatedTopics" array
|
|||
|
|
local start_idx = body:find('"RelatedTopics"%s*:%s*%[')
|
|||
|
|
if not start_idx then return nil end
|
|||
|
|
-- Look for the first Text inside that array
|
|||
|
|
local v = body:sub(start_idx):match('"Text"%s*:%s*"(.-)"')
|
|||
|
|
if not v or v == "" then return nil end
|
|||
|
|
v = v:gsub('\\"', '"'):gsub('\\\\', '\\'):gsub('\\n', ' '):gsub('\\t', ' ')
|
|||
|
|
v = v:gsub("%s+", " "):gsub("^%s+", ""):gsub("%s+$", "")
|
|||
|
|
-- Trim absurdly long blurbs to the first ~300 chars
|
|||
|
|
if #v > 320 then v = v:sub(1, 300) .. "…" end
|
|||
|
|
return v
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
local answer =
|
|||
|
|
field("AbstractText")
|
|||
|
|
or field("Answer")
|
|||
|
|
or field("Definition")
|
|||
|
|
or first_related()
|
|||
|
|
|
|||
|
|
if answer then
|
|||
|
|
jarvis.log("info", "ddg answer found, " .. #answer .. " chars")
|
|||
|
|
return jarvis.cmd.ok(answer)
|
|||
|
|
end
|
|||
|
|
|
|||
|
|
-- Nothing useful — open the search page so the user can read raw results.
|
|||
|
|
local fallback = "https://duckduckgo.com/?q=" .. url_encode(query)
|
|||
|
|
jarvis.system.open(fallback)
|
|||
|
|
return jarvis.cmd.ok(lang == "ru"
|
|||
|
|
and ("Ничего конкретного не нашёл, открываю поиск по запросу: " .. query)
|
|||
|
|
or ("Nothing direct found, opening search for: " .. query))
|