From ab71bd7da9f94aa97258b4b546cf742e8155f66f Mon Sep 17 00:00:00 2001 From: Justin Oro Date: Fri, 11 Sep 2026 12:11:00 -0700 Subject: [PATCH] boost mapgenie.io in wiki search results for more reliable map links --- README.md | 2 +- index.js | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fd1c9e7..f3d7998 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ To change the token or key later, edit `.env` directly, then run the installer a - The bot only replies to mentions of its trigger word if Ollama is installed, running, and has the model in `OLLAMA_MODEL` downloaded - without that, mentioning it gets no reply at all, and nothing is logged (check `bot.error.log` if you expect it to be working and it isn't replying). Each person can only trigger an AI answer once every 5 seconds; asking again sooner (or the Ollama request failing) just gets no reply rather than a second request, which keeps a burst of questions from bogging down your computer generating several replies at once. - `!remember SOMETHING` teaches it a permanent fact - a name, a preference, a correction like "don't call me buddy", or something about another member mentioned by name - that it will always take into account, even after the bot restarts. These facts are shared: anyone can ask about a fact someone else taught it, not just the person who typed `!remember`. They're saved to `memory.yaml` under whoever typed the command, and never expire on their own, unlike the short-term memory described below. `!forget SOMETHING` removes one you personally taught it (it has to match what you typed to `!remember`, word for word - use `!memories` to see the exact wording it has stored under your name), and `!forget all` clears everything you've had it remember. Each person can have up to 20 things remembered at a time. - `OLLAMA_MODEL` in `.env` controls which model it uses (`llama3.2` by default - small, fast, and good enough for short in-character replies). To use a different one, pull it yourself with `ollama pull MODEL_NAME`, set `OLLAMA_MODEL=MODEL_NAME` in `.env`, and restart it (re-run the installer, or `pm2 restart discord-bot`). Bigger models give better answers but take longer to reply and use more of your computer's memory. -- `TAVILY_API_KEY` in `.env` is optional and off by default, and is what `!wiki QUESTION` needs to work. Without it, `!wiki` just says AI isn't set up for that. With it set, `!wiki` runs a real web search and answers with what it finds, which for specific game facts (an exact item location, a boss's weak point) is far more likely to be accurate than the small local model guessing on its own - unlike the AI chat itself, this does send your question to Tavily's search API over the internet. Get a free key (no credit card required, 1,000 searches a month free) at [app.tavily.com](https://app.tavily.com/) (the installer asks for this too and lets you skip it), then restart the bot (re-run the installer, or `pm2 restart discord-bot`) to pick it up. Plain mentions of the trigger word never use this - they always stay pure in-character chat from the local model, so `!wiki` is the one to reach for when you want a real answer instead of banter. +- `TAVILY_API_KEY` in `.env` is optional and off by default, and is what `!wiki QUESTION` needs to work. Without it, `!wiki` just says AI isn't set up for that. With it set, `!wiki` runs a real web search and answers with what it finds, which for specific game facts (an exact item location, a boss's weak point) is far more likely to be accurate than the small local model guessing on its own - unlike the AI chat itself, this does send your question to Tavily's search API over the internet. Get a free key (no credit card required, 1,000 searches a month free) at [app.tavily.com](https://app.tavily.com/) (the installer asks for this too and lets you skip it), then restart the bot (re-run the installer, or `pm2 restart discord-bot`) to pick it up. Plain mentions of the trigger word never use this - they always stay pure in-character chat from the local model, so `!wiki` is the one to reach for when you want a real answer instead of banter. If the search results for a `!wiki` answer happen to include a page from [mapgenie.io](https://mapgenie.io) (interactive maps for a lot of survival/open-world games), it adds that link to the reply too - there's no per-game setup for this, it just checks whatever the search turns up. - It remembers each person's last 3 exchanges (their message and its reply) so it can follow up naturally and stick to things you told it earlier in the conversation, like a correction or a preference. This memory is per-person, kept only in the bot's running memory (not saved to disk), and forgotten automatically after 30 minutes of that person not mentioning it. - The bot's name, trigger word, and personality all come from `personality.yaml`. It ships set up as BMO from Adventure Time: ``` diff --git a/index.js b/index.js index ee67110..190c61e 100644 --- a/index.js +++ b/index.js @@ -82,6 +82,14 @@ function stripHtml(text) { return (text || "").replace(/<\/?[^>]+(>|$)/g, ""); } +const MAP_SITE_PATTERN = /mapgenie\.io/i; + +function findMapLink(results) { + if (!results) return null; + const hit = results.find((r) => r.url && MAP_SITE_PATTERN.test(r.url)); + return hit ? hit.url : null; +} + async function searchWeb(query) { if (!WEB_SEARCH_ENABLED || !query) return { answer: null, results: [] }; const controller = new AbortController(); @@ -97,7 +105,9 @@ async function searchWeb(query) { query: query, max_results: WEB_SEARCH_RESULT_COUNT, search_depth: "advanced", - include_answer: true + include_answer: true, + include_domains: ["mapgenie.io"], + include_domains_mode: "boost" }), signal: controller.signal }); @@ -1620,7 +1630,9 @@ async function handleWiki(message, query) { ? capToSentences(searchResults.answer.trim(), AI_MAX_SENTENCES) : await askAI(query, history, displayName, allFacts, searchResults); if (aiReply) { - await message.reply(aiReply); + const mapLink = findMapLink(searchResults && searchResults.results); + const content = aiReply + (mapLink ? "\nInteractive map: " + mapLink : ""); + await message.reply(content); rememberAiExchange(message.author.id, query, aiReply); } } catch (err) {