allow docker command to work in DMs

This commit is contained in:
Justin Oros
2026-09-25 18:31:46 -07:00
parent 5ff8bbbc9b
commit b5dfa7cd14
2 changed files with 21 additions and 6 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ To change the token or key later, edit `.env` directly, then run the installer a
- `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.
- `!own GAME NAME` automatically creates a Discord role for that game (if one doesn't already exist) and gives it to you, each with its own randomly assigned emoji set as that role's actual Discord icon (the small badge next to the role name), not just text stuck in front of it. A handful of servers don't support role icons, in which case it falls back to putting the emoji in the role's name instead - this is automatic, no setup needed. `!role GAME NAME` does the same without marking it owned, for anyone who just wants to be pingable for a game without confirming ownership. When no emoji is picked for you, and AI is set up (see `TAVILY_API_KEY`/Ollama notes below), it asks the local AI model to suggest one that actually fits the game instead of a purely random one - if AI isn't available, or it doesn't reply with a usable emoji, it falls back to a random one the same as before. To pick your own emoji instead of the suggested one, put it right after `!role`: `!role 🌴 Green Hell`. This also works on a role that already exists, to change its emoji at any time - no need to touch `roles.yaml` by hand unless you'd rather do it that way. `!unroll GAME NAME` removes the role from you personally, it doesn't delete the role itself. `!roles` lists every game role that exists and how many members currently have each one, with clickable buttons (or a dropdown once there are more than 25 roles) underneath so people can join or leave a role with one click instead of typing a command. Clicking a role you already have removes it, same as `!unroll`. New members get this same clickable list posted right after their welcome greeting, so joining a role for the games they own or play takes one click. All of this is tracked in `roles.yaml` in this folder (game name, Discord role ID, and its emoji), left out of git the same as the other data files. There's deliberately no command to delete a role entirely - if you want one gone, delete it from your server's role list in Discord and remove its line from `roles.yaml` by hand. If you're upgrading an existing install, the bot needs the "Manage Roles" permission to use this - grant it under Server Settings > Roles for the bot's role, or re-run the OAuth2 authorize URL from step 5 above. The bot's own role also needs to sit above any role it creates in your server's role list (Server Settings > Roles, drag order) or it won't be able to assign them.
- `!docker ps` and `!docker restart CONTAINER NAME` let people with a specific Discord role manage Docker containers on a remote server over SSH (handy for restarting a game server without shelling in yourself). Optional and off by default - set `DOCKER_SSH_HOST`, `DOCKER_SSH_USER`, `DOCKER_SSH_KEY_PATH`, and `DOCKER_ROLE_ID` in `.env` to turn it on (the installer asks for these too and lets you skip them). The bot connects with the system `ssh` command using that key, so SSH key access from wherever the bot runs to that host needs to already work before you set this up. `DOCKER_ROLE_ID` is the numeric Discord role ID required to use either command - right-click the role in Discord (with Developer Mode on under User Settings > Advanced) and choose Copy Role ID. `DOCKER_PROTECTED_CONTAINERS` is an optional comma-separated list of container names (e.g. `Plex-Media-Server`) that `!docker restart` will always refuse, no matter who asks. `!docker restart` matches by container name (not case-sensitive) or container ID. Anyone without the role gets a permission error; nothing here works in DMs.
- `!docker ps` and `!docker restart CONTAINER NAME` let people with a specific Discord role manage Docker containers on a remote server over SSH (handy for restarting a game server without shelling in yourself). Optional and off by default - set `DOCKER_SSH_HOST`, `DOCKER_SSH_USER`, `DOCKER_SSH_KEY_PATH`, and `DOCKER_ROLE_ID` in `.env` to turn it on (the installer asks for these too and lets you skip them). The bot connects with the system `ssh` command using that key, so SSH key access from wherever the bot runs to that host needs to already work before you set this up. `DOCKER_ROLE_ID` is the numeric Discord role ID required to use either command - right-click the role in Discord (with Developer Mode on under User Settings > Advanced) and choose Copy Role ID. `DOCKER_PROTECTED_CONTAINERS` is an optional comma-separated list of container names (e.g. `Plex-Media-Server`) that `!docker restart` will always refuse, no matter who asks. `!docker restart` matches by container name (not case-sensitive) or container ID. Anyone without the role gets a permission error. Unlike every other command here, this one also works in a DM to the bot, not just in a server text channel.
- `!wiki` also links an interactive map from [mapgenie.io](https://mapgenie.io) when it has one for the game being asked about, using `maps.json` in this folder - a plain list of `"game name": "mapgenie URL"` pairs. It ships with one entry (Sons Of The Forest) as an example; add more yourself as you find them by browsing to the game on mapgenie.io and copying its URL. This is a direct lookup, not a live search, because mapgenie's map pages are built with client-side JavaScript that search engines mostly can't read the content of, so relying on search alone to find them essentially never works even when a page exists. Edit `maps.json` and restart the bot (re-run the installer, or `pm2 restart discord-bot`) to pick up changes.
- 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:
+20 -5
View File
@@ -2128,16 +2128,26 @@ async function handleDockerRestart(message, name) {
}
}
async function hasDockerRole(message) {
if (message.guild) {
return message.member.roles.cache.has(DOCKER_ROLE_ID);
}
for (const guild of message.client.guilds.cache.values()) {
let member = guild.members.cache.get(message.author.id);
if (!member) {
member = await guild.members.fetch(message.author.id).catch(() => null);
}
if (member && member.roles.cache.has(DOCKER_ROLE_ID)) return true;
}
return false;
}
async function handleDocker(message, input) {
if (!DOCKER_ENABLED) {
await message.reply("Docker control isn't set up. Set DOCKER_SSH_HOST, DOCKER_SSH_USER, DOCKER_SSH_KEY_PATH, and DOCKER_ROLE_ID in .env to enable it.");
return;
}
if (!message.guild) {
await message.reply("!docker only works inside a server, not in DMs.");
return;
}
if (!message.member.roles.cache.has(DOCKER_ROLE_ID)) {
if (!(await hasDockerRole(message))) {
await message.reply("You don't have permission to use !docker.");
return;
}
@@ -2516,6 +2526,11 @@ client.on("messageCreate", async (message) => {
return;
}
if (dmContent === "!docker" || dmContent.startsWith("!docker ")) {
await handleDocker(message, message.content.trim().slice(7).trim());
return;
}
const displayName = message.author.globalName || message.author.username;
if (dmGreetedUsers.has(message.author.id)) {
await message.reply("Type !help for a list of my commands to be used in Text Channels.");