-- DuckDuckGo Instant Answer — short factual lookups WITHOUT an API key. -- -- Endpoint: https://api.duckduckgo.com/?q=&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))