Search: DuckDuckGo instant answer card
build-container / build (push) Successful in 2m5s

The server asks DuckDuckGo's instant-answer service (summary, definition, quick
answers) and Search shows it as a card above the app's own results when there
is one. Lookups are cached for 10 minutes and a failure never breaks Search.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-20 15:31:42 -04:00
1 parent 64c91f9e0a
commit 18266267e5
2 files changed
+33 -3

No files matched your search

+23
View File
@@ -270,6 +270,28 @@ async function plexProxy(req, res) {
console.log(`[data] ${dir}: ${count('passkeys.json')} passkeys, ${count('calendars.json')} calendars`);
}
// ---- DuckDuckGo instant answers for the Search tab (a short summary when DDG has one; many queries return nothing)
const ddgCache = new Map();
async function ddgAnswer(req, res) {
const q = (new URL(req.url, 'http://x').searchParams.get('q') || '').trim().slice(0, 200);
if (q.length < 2) return json(res, 200, {});
const hit = ddgCache.get(q.toLowerCase());
if (hit && Date.now() - hit.t < 10 * 60e3) return json(res, 200, hit.v);
try {
const u = new URL('https://api.duckduckgo.com/');
u.search = new URLSearchParams({ q, format: 'json', no_html: '1', skip_disambig: '1', t: 'nehub' });
const r = await fetch(u, { signal: AbortSignal.timeout(6000), headers: { Accept: 'application/json' } });
const d = await r.json();
const v = {};
if (d.AbstractText) Object.assign(v, { heading: d.Heading, text: d.AbstractText.slice(0, 600), source: d.AbstractSource, url: d.AbstractURL, image: d.Image ? 'https://duckduckgo.com' + d.Image : '' });
else if (d.Definition) Object.assign(v, { heading: d.Heading || q, text: d.Definition.slice(0, 600), source: d.DefinitionSource, url: d.DefinitionURL });
if (d.Answer && typeof d.Answer === 'string') v.answer = d.Answer.slice(0, 200);
if (ddgCache.size > 200) ddgCache.clear();
ddgCache.set(q.toLowerCase(), { t: Date.now(), v });
json(res, 200, v);
} catch { json(res, 200, {}); } // a failed lookup must never break Search
}
if (!JWT_SECRET) console.warn('WARNING: ONLYOFFICE_JWT_SECRET not set. In-app editing only works if the Document Server has JWT disabled, and save callbacks are unauthenticated. Testing only.');
http.createServer((req, res) => {
@@ -289,6 +311,7 @@ http.createServer((req, res) => {
}
if (req.method === 'POST' && req.url === '/api/oo/config') return ooConfig(req, res);
if (req.method === 'POST' && req.url.startsWith('/api/oo/callback')) return ooCallback(req, res);
if (pathname === '/api/ddg') return ddgAnswer(req, res);
if (req.url.startsWith('/api/cal/')) return cal.handle(req, res);
if (req.url.startsWith('/api/plex/')) return plexApi(req, res);
if (req.url.startsWith('/plex/') && req.method === 'GET') return plexProxy(req, res);