Add trivia scoring with !score leaderboard

This commit is contained in:
Justin Oro
2026-09-06 16:33:42 -07:00
parent da8b83478a
commit 37c55abcbd
5 changed files with 169 additions and 10 deletions
+1
View File
@@ -4,6 +4,7 @@ games.json
owned.json owned.json
steamlinks.json steamlinks.json
memory.yaml memory.yaml
scores.json
.link-steam-profiles-owner.json .link-steam-profiles-owner.json
bot.log bot.log
bot.error.log bot.error.log
+7 -7
View File
@@ -55,12 +55,12 @@ Either way, it will ask for your Discord bot token, your IsThereAnyDeal key, and
In your Discord server, try `!watch Hades` and `!list` to confirm it is working. In your Discord server, try `!watch Hades` and `!list` to confirm it is working.
Logs go to `bot.log` and `bot.error.log` inside the project folder, useful if a command does not seem to be working. `pm2 logs discord-game-price-bot` shows the same thing live in the terminal, and `pm2 status` shows whether it's currently running. Logs go to `bot.log` and `bot.error.log` inside the project folder, useful if a command does not seem to be working. `pm2 logs discord-bot` shows the same thing live in the terminal, and `pm2 status` shows whether it's currently running.
To stop the bot: To stop the bot:
``` ```
pm2 stop discord-game-price-bot pm2 stop discord-bot
``` ```
To change the token or key later, edit `.env` directly, then run the installer again (`bash install.sh` or the PowerShell command above) to restart it with the new values. Re-running it any time (for example after editing `index.js`) is safe, it just reinstalls and restarts the service. To change the token or key later, edit `.env` directly, then run the installer again (`bash install.sh` or the PowerShell command above) to restart it with the new values. Re-running it any time (for example after editing `index.js`) is safe, it just reinstalls and restarts the service.
@@ -73,11 +73,11 @@ To change the token or key later, edit `.env` directly, then run the installer a
- The daily check only alerts once per discount level per game. If the price drops further while still on sale, you get a new alert. If it goes back to full price and later goes on sale again, that is a new alert too. - The daily check only alerts once per discount level per game. If the price drops further while still on sale, you get a new alert. If it goes back to full price and later goes on sale again, that is a new alert too.
- Everything watched is stored in `games.json` in this folder, in plain text, so you can also open it directly if you ever want to see or edit the list by hand. - Everything watched is stored in `games.json` in this folder, in plain text, so you can also open it directly if you ever want to see or edit the list by hand.
- `games.json`, `owned.json`, `steamlinks.json`, `memory.yaml`, and `.env` hold your server's actual data and are left out of git (`.gitignore`) so they never end up in a public repo by accident. The installer creates each of these from its matching `.example` template automatically if it doesn't exist yet. - `games.json`, `owned.json`, `steamlinks.json`, `memory.yaml`, and `.env` hold your server's actual data and are left out of git (`.gitignore`) so they never end up in a public repo by accident. The installer creates each of these from its matching `.example` template automatically if it doesn't exist yet.
- The installer manages the bot with [pm2](https://pm2.keymetrics.io), so once it's set up you can check on it with plain pm2 commands any time: `pm2 status` (is it running), `pm2 logs discord-game-price-bot` (live logs), `pm2 restart discord-game-price-bot`, `pm2 stop discord-game-price-bot`, and `pm2 delete discord-game-price-bot` (remove it from pm2 entirely - re-run the installer to add it back). - The installer manages the bot with [pm2](https://pm2.keymetrics.io), so once it's set up you can check on it with plain pm2 commands any time: `pm2 status` (is it running), `pm2 logs discord-bot` (live logs), `pm2 restart discord-bot`, `pm2 stop discord-bot`, and `pm2 delete discord-bot` (remove it from pm2 entirely - re-run the installer to add it back).
- If you're upgrading from an older version of this bot that used launchd directly on macOS, `install.sh` detects and automatically removes that old service the first time you run it, so you don't end up with two copies of the bot running and double-replying in Discord. - If you're upgrading from an older version of this bot that used launchd directly on macOS, `install.sh` detects and automatically removes that old service the first time you run it, so you don't end up with two copies of the bot running and double-replying in Discord.
- To add, remove, or change greetings, edit `greetings.yaml`. Use `{user}` anywhere in a greeting and it gets replaced with a mention of the person who joined. - To add, remove, or change greetings, edit `greetings.yaml`. Use `{user}` anywhere in a greeting and it gets replaced with a mention of the person who joined.
- Same for goodbyes: edit `goodbyes.yaml`. Use `{user}` there too, it gets replaced with the username of whoever left (a plain name, not a mention, since Discord can't ping someone no longer in the server). - Same for goodbyes: edit `goodbyes.yaml`. Use `{user}` there too, it gets replaced with the username of whoever left (a plain name, not a mention, since Discord can't ping someone no longer in the server).
- The join greeting posts to whichever channel is named `general` in your server. To use a different channel, change `GREETING_CHANNEL_NAME` near the top of `index.js` to that channel's name (no `#`) and restart it (re-run the installer, or `pm2 restart discord-game-price-bot`). If no channel with that name exists, nothing posts and `bot.error.log` will note it. - The join greeting posts to whichever channel is named `general` in your server. To use a different channel, change `GREETING_CHANNEL_NAME` near the top of `index.js` to that channel's name (no `#`) and restart it (re-run the installer, or `pm2 restart discord-bot`). If no channel with that name exists, nothing posts and `bot.error.log` will note it.
- Any Discord Scheduled Event on your server gets an automatic reminder posted to that same channel 15 minutes before it starts, like "Movie Night starting in 15 minutes!" Each event only gets reminded once. To change how far ahead it reminds, edit `EVENT_REMINDER_MINUTES` near the top of `index.js` and restart it. - Any Discord Scheduled Event on your server gets an automatic reminder posted to that same channel 15 minutes before it starts, like "Movie Night starting in 15 minutes!" Each event only gets reminded once. To change how far ahead it reminds, edit `EVENT_REMINDER_MINUTES` near the top of `index.js` and restart it.
- `!info` gets platforms and Multiplayer/Co-Op/Cross-Play from that game's Steam store page when it has one. For non-Steam games (GOG/Epic exclusives etc.) there is no such lookup available, so "not listed" there means "couldn't confirm it," not "definitely single-player only." - `!info` gets platforms and Multiplayer/Co-Op/Cross-Play from that game's Steam store page when it has one. For non-Steam games (GOG/Epic exclusives etc.) there is no such lookup available, so "not listed" there means "couldn't confirm it," not "definitely single-player only."
- The ownership count on `!info` combines two sources: anyone who ran `!own` for that game, plus anyone who linked their Steam profile with `!my-steam-profile` and actually owns it there. Discord itself has no concept of game ownership, so there is no fully automatic way to get this - `!own` needs people to mark their own games by hand, and `!my-steam-profile` only auto-detects Steam copies (not GOG/Epic) and only if that person's Steam profile has "game details" set to public. Everything self-reported through `!own` is stored in `owned.json`; Steam profile links are stored in `steamlinks.json`, both in this folder, in plain text. - The ownership count on `!info` combines two sources: anyone who ran `!own` for that game, plus anyone who linked their Steam profile with `!my-steam-profile` and actually owns it there. Discord itself has no concept of game ownership, so there is no fully automatic way to get this - `!own` needs people to mark their own games by hand, and `!my-steam-profile` only auto-detects Steam copies (not GOG/Epic) and only if that person's Steam profile has "game details" set to public. Everything self-reported through `!own` is stored in `owned.json`; Steam profile links are stored in `steamlinks.json`, both in this folder, in plain text.
@@ -85,7 +85,7 @@ To change the token or key later, edit `.env` directly, then run the installer a
- If you don't want to make each person run `!my-steam-profile` themselves, you can link them yourself from the terminal (the easiest way if you already know your server members as Steam friends): run `bash install.sh --link-steam-profiles` (macOS/Linux) or `powershell -ExecutionPolicy Bypass -File install.ps1 -LinkSteamProfiles` (Windows). The first time, it asks for your own Steam profile once (to read your friends list from - it needs your Steam friends list privacy set to public, and remembers your profile after that so it won't ask again). Then it lists your Discord server's members for you to pick one by number, lists your Steam friends for you to pick one by number, links them, and asks if you want to link another (defaults to no). This does not restart the bot, so there is no need to run the installer again afterward. This is terminal-only by design - there's no Discord command for it, so members can't link (or relink) each other's profiles themselves. - If you don't want to make each person run `!my-steam-profile` themselves, you can link them yourself from the terminal (the easiest way if you already know your server members as Steam friends): run `bash install.sh --link-steam-profiles` (macOS/Linux) or `powershell -ExecutionPolicy Bypass -File install.ps1 -LinkSteamProfiles` (Windows). The first time, it asks for your own Steam profile once (to read your friends list from - it needs your Steam friends list privacy set to public, and remembers your profile after that so it won't ask again). Then it lists your Discord server's members for you to pick one by number, lists your Steam friends for you to pick one by number, links them, and asks if you want to link another (defaults to no). This does not restart the bot, so there is no need to run the installer again afterward. This is terminal-only by design - there's no Discord command for it, so members can't link (or relink) each other's profiles themselves.
- 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. - 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. - `!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-game-price-bot`). Bigger models give better answers but take longer to reply and use more of your computer's memory. - `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.
- 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. - 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: - The bot's name, trigger word, and personality all come from `personality.yaml`. It ships set up as BMO from Adventure Time:
``` ```
@@ -96,8 +96,8 @@ To change the token or key later, edit `.env` directly, then run the installer a
a little literal-minded, and love games and your friends Finn and Jake. Playful, warm, a little silly, a little literal-minded, and love games and your friends Finn and Jake. Playful, warm, a little silly,
occasionally saying things like "Oh my glob" or calling the user "buddy" or "friend". occasionally saying things like "Oh my glob" or calling the user "buddy" or "friend".
``` ```
Change `name` to whatever the bot should be called (used in a couple of log messages), `trigger` to the word that should make it reply (matched as a whole word, case-insensitive, anywhere in a message), and `persona` to a description of how it should talk and act - write it like you're describing a character to someone. It always stays an expert on this README and keeps replies to one short sentence, no matter what personality you give it. Edit the file and restart it (re-run the installer, or `pm2 restart discord-game-price-bot`) to pick up the changes. `greetings.yaml` and `goodbyes.yaml` have their own sample lines written in BMO's voice too - edit those separately if you want the join/leave messages to match a different personality. Change `name` to whatever the bot should be called (used in a couple of log messages), `trigger` to the word that should make it reply (matched as a whole word, case-insensitive, anywhere in a message), and `persona` to a description of how it should talk and act - write it like you're describing a character to someone. It always stays an expert on this README and keeps replies to one short sentence, no matter what personality you give it. Edit the file and restart it (re-run the installer, or `pm2 restart discord-bot`) to pick up the changes. `greetings.yaml` and `goodbyes.yaml` have their own sample lines written in BMO's voice too - edit those separately if you want the join/leave messages to match a different personality.
- To turn the AI answers off without uninstalling Ollama (for example if you want your computer's resources back for something else), set `AI_ENABLED=false` in `.env` and restart it (re-run the installer, or `pm2 restart discord-game-price-bot`). Set it back to `true` (or remove the line entirely) to turn it back on. This only affects mention replies - everything else keeps working either way. - To turn the AI answers off without uninstalling Ollama (for example if you want your computer's resources back for something else), set `AI_ENABLED=false` in `.env` and restart it (re-run the installer, or `pm2 restart discord-bot`). Set it back to `true` (or remove the line entirely) to turn it back on. This only affects mention replies - everything else keeps working either way.
## License ## License
+151 -1
View File
@@ -21,10 +21,13 @@ const GREETING_CHANNEL_NAME = "general";
const EVENT_REMINDER_MINUTES = 15; const EVENT_REMINDER_MINUTES = 15;
const POPULAR_TOP_N = 10; const POPULAR_TOP_N = 10;
const ALL_PRICES_TOP_N = 10; const ALL_PRICES_TOP_N = 10;
const TRIVIA_POINTS = 100;
const SCORE_TOP_N = 10;
const ITAD_BASE = "https://api.isthereanydeal.com"; const ITAD_BASE = "https://api.isthereanydeal.com";
const GAMES_PATH = path.join(__dirname, "games.json"); const GAMES_PATH = path.join(__dirname, "games.json");
const OWNED_PATH = path.join(__dirname, "owned.json"); const OWNED_PATH = path.join(__dirname, "owned.json");
const STEAMLINKS_PATH = path.join(__dirname, "steamlinks.json"); const STEAMLINKS_PATH = path.join(__dirname, "steamlinks.json");
const SCORES_PATH = path.join(__dirname, "scores.json");
const GREETINGS_PATH = path.join(__dirname, "greetings.yaml"); const GREETINGS_PATH = path.join(__dirname, "greetings.yaml");
const GOODBYES_PATH = path.join(__dirname, "goodbyes.yaml"); const GOODBYES_PATH = path.join(__dirname, "goodbyes.yaml");
const README_PATH = path.join(__dirname, "README.md"); const README_PATH = path.join(__dirname, "README.md");
@@ -244,6 +247,21 @@ function saveSteamLinks(links) {
fs.writeFileSync(STEAMLINKS_PATH, JSON.stringify(links, null, 2) + "\n"); fs.writeFileSync(STEAMLINKS_PATH, JSON.stringify(links, null, 2) + "\n");
} }
function loadScores() {
return JSON.parse(fs.readFileSync(SCORES_PATH, "utf8"));
}
function saveScores(scores) {
fs.writeFileSync(SCORES_PATH, JSON.stringify(scores, null, 2) + "\n");
}
function awardTriviaPoints(userId) {
const scores = loadScores();
scores[userId] = (scores[userId] || 0) + TRIVIA_POINTS;
saveScores(scores);
return scores[userId];
}
function loadMemory() { function loadMemory() {
try { try {
const data = yaml.load(fs.readFileSync(MEMORY_PATH, "utf8")); const data = yaml.load(fs.readFileSync(MEMORY_PATH, "utf8"));
@@ -1352,6 +1370,128 @@ async function handleShops(message) {
} }
} }
const TRIVIA_TIME_LIMIT_MS = 30 * 1000;
const TRIVIA_LETTERS = ["A", "B", "C", "D"];
const activeTrivia = new Map();
const TRIVIA_QUESTIONS = [
{ question: "Which company developed the original Half-Life?", choices: ["Valve", "id Software", "Epic Games", "Bethesda"], answer: 0 },
{ question: "What year did Steam first launch?", choices: ["2001", "2003", "2005", "2008"], answer: 1 },
{ question: "Which studio made Minecraft?", choices: ["Mojang", "Notch Games", "Microsoft Studios", "Double Fine"], answer: 0 },
{ question: "What engine powers Fortnite?", choices: ["Source", "Unity", "Unreal Engine", "CryEngine"], answer: 2 },
{ question: "Which game popularized the battle royale genre on PC?", choices: ["Fortnite", "PUBG", "Apex Legends", "Warzone"], answer: 1 },
{ question: "Who is the developer of the Elder Scrolls series?", choices: ["BioWare", "Bethesda Game Studios", "CD Projekt Red", "Obsidian"], answer: 1 },
{ question: "What was the first game in the Half-Life series to introduce Alyx Vance?", choices: ["Half-Life", "Half-Life 2", "Half-Life: Opposing Force", "Half-Life: Blue Shift"], answer: 1 },
{ question: "Which company created the Unreal Engine?", choices: ["Epic Games", "id Software", "Valve", "Crytek"], answer: 0 },
{ question: "What is the best-selling PC game franchise made by Blizzard, known for orcs versus humans?", choices: ["StarCraft", "Diablo", "Warcraft", "Overwatch"], answer: 2 },
{ question: "Which studio developed The Witcher 3: Wild Hunt?", choices: ["CD Projekt Red", "11 bit studios", "Techland", "People Can Fly"], answer: 0 },
{ question: "What year was the original Doom released?", choices: ["1990", "1993", "1996", "1999"], answer: 1 },
{ question: "Which company publishes the Steam storefront?", choices: ["Epic Games", "GOG", "Valve", "Microsoft"], answer: 2 },
{ question: "What is the name of the AI companion in Portal?", choices: ["GLaDOS", "Cortana", "HAL", "SHODAN"], answer: 0 },
{ question: "Which game is credited as one of the first major esports titles on PC?", choices: ["StarCraft: Brood War", "Minecraft", "The Sims", "Half-Life 2"], answer: 0 },
{ question: "What company developed the Source engine?", choices: ["Valve", "id Software", "Epic Games", "Bethesda"], answer: 0 },
{ question: "Which platform, launched by CD Projekt, is a DRM-free game store?", choices: ["Origin", "GOG", "itch.io", "Epic Games Store"], answer: 1 },
{ question: "What is the name of the map editor built into many Bethesda games like Skyrim?", choices: ["Hammer", "Creation Kit", "UnrealEd", "Forge"], answer: 1 },
{ question: "Which game series features the character Master Chief?", choices: ["Gears of War", "Halo", "Destiny", "Titanfall"], answer: 1 },
{ question: "Who developed the game engine CryEngine?", choices: ["Crytek", "id Software", "Epic Games", "Valve"], answer: 0 },
{ question: "What year did the Epic Games Store launch?", choices: ["2016", "2018", "2020", "2013"], answer: 1 },
{ question: "Which game is known for popularizing the tower defense genre as a standalone PC hit?", choices: ["Plants vs. Zombies", "Bloons TD", "Dungeon Defenders", "Orcs Must Die!"], answer: 0 },
{ question: "What studio created the Portal series?", choices: ["Valve", "id Software", "Arkane Studios", "Remedy Entertainment"], answer: 0 },
{ question: "Which game was developed by Klei Entertainment and involves surviving on an alien planet?", choices: ["Don't Starve", "Oxygen Not Included", "Subnautica", "Raft"], answer: 1 },
{ question: "Which company originally created and released Counter-Strike as a Half-Life mod?", choices: ["Valve", "a group of independent modders", "id Software", "Gearbox Software"], answer: 1 },
{ question: "What year was the first Grand Theft Auto game released on PC?", choices: ["1994", "1997", "2001", "2004"], answer: 1 },
{ question: "Which developer is known for the Souls series, including Dark Souls and Elden Ring?", choices: ["FromSoftware", "Team Ninja", "Capcom", "PlatinumGames"], answer: 0 },
{ question: "What was the codename/engine used for id Software's Doom (2016) and Doom Eternal?", choices: ["id Tech", "Frostbite", "REDengine", "Anvil"], answer: 0 },
{ question: "Which sandbox survival game was created by Facepunch Studios?", choices: ["Rust", "Ark: Survival Evolved", "7 Days to Die", "DayZ"], answer: 0 },
{ question: "What game is widely credited as the first commercially successful real-time strategy game?", choices: ["Command & Conquer", "Dune II", "Warcraft: Orcs & Humans", "Age of Empires"], answer: 1 },
{ question: "Which company developed and publishes World of Warcraft?", choices: ["Blizzard Entertainment", "Bethesda", "Square Enix", "NCSoft"], answer: 0 }
];
function pickTriviaQuestion() {
return TRIVIA_QUESTIONS[Math.floor(Math.random() * TRIVIA_QUESTIONS.length)];
}
async function handleTrivia(message) {
const channelId = message.channelId;
if (activeTrivia.has(channelId)) {
await message.reply("A trivia question is already up in this channel, answer that one first!");
return;
}
const picked = pickTriviaQuestion();
const entry = { question: picked, timeout: null };
activeTrivia.set(channelId, entry);
const lines = ["PC gaming trivia! " + picked.question];
picked.choices.forEach((choice, i) => lines.push(TRIVIA_LETTERS[i] + ") " + choice));
lines.push("You have 30 seconds, first correct answer wins!");
try {
await message.reply(lines.join("\n"));
} catch (err) {
console.error("Trivia post failed:", err.message);
activeTrivia.delete(channelId);
return;
}
entry.timeout = setTimeout(async () => {
if (activeTrivia.get(channelId) !== entry) return;
activeTrivia.delete(channelId);
try {
await message.channel.send(
"Time's up! The answer was " + TRIVIA_LETTERS[picked.answer] + ") " + picked.choices[picked.answer] + "."
);
} catch (err) {
console.error("Trivia timeout message failed:", err.message);
}
}, TRIVIA_TIME_LIMIT_MS);
}
async function handleTriviaAnswer(message, content) {
const channelId = message.channelId;
const entry = activeTrivia.get(channelId);
if (!entry) return false;
const cleaned = content.trim().toUpperCase().replace(/[).:]/g, "");
const guessIndex = TRIVIA_LETTERS.indexOf(cleaned);
if (guessIndex === -1) return false;
if (guessIndex !== entry.question.answer) return false;
clearTimeout(entry.timeout);
activeTrivia.delete(channelId);
const displayName = (message.member && message.member.displayName) ||
message.author.globalName || message.author.username;
const total = awardTriviaPoints(message.author.id);
await message.reply(
"Correct, " + displayName + "! The answer was " + TRIVIA_LETTERS[entry.question.answer] + ") " +
entry.question.choices[entry.question.answer] + ". +" + TRIVIA_POINTS + " points (" + total + " total)."
);
return true;
}
async function handleScore(message) {
const scores = loadScores();
const entries = Object.entries(scores)
.filter(([, points]) => points > 0)
.sort((a, b) => b[1] - a[1])
.slice(0, SCORE_TOP_N);
if (entries.length === 0) {
await message.reply("Nobody has scored any trivia points yet! Type !trivia to start a round.");
return;
}
const lines = ["Top trivia scores:"];
entries.forEach((entry, i) => {
const userId = entry[0];
const points = entry[1];
lines.push((i + 1) + ". <@" + userId + "> - " + points + " point" + (points === 1 ? "" : "s"));
});
await message.reply({ content: lines.join("\n"), allowedMentions: { users: [] } });
}
async function handleHelp(message, note) { async function handleHelp(message, note) {
await message.reply( await message.reply(
"Commands:\n" + "Commands:\n" +
@@ -1372,7 +1512,9 @@ async function handleHelp(message, note) {
"!forget SOMETHING - make me forget something you had me remember (must match exactly), or !forget all\n" + "!forget SOMETHING - make me forget something you had me remember (must match exactly), or !forget all\n" +
"!memories - show everything I remember about you\n" + "!memories - show everything I remember about you\n" +
"!check - run the sale check right now instead of waiting for the daily run\n" + "!check - run the sale check right now instead of waiting for the daily run\n" +
"!shops - list every store I check prices at" + "!shops - list every store I check prices at\n" +
"!trivia - PC gaming trivia, multiple choice, first correct answer in chat wins 100 points\n" +
"!score - show the top 10 members by trivia points" +
(note ? "\n\n" + note : "") (note ? "\n\n" + note : "")
); );
} }
@@ -1581,6 +1723,10 @@ client.on("messageCreate", async (message) => {
const content = message.content.trim(); const content = message.content.trim();
const lower = content.toLowerCase(); const lower = content.toLowerCase();
if (!content.startsWith("!") && (await handleTriviaAnswer(message, content))) {
return;
}
if (lower.startsWith("!watch ")) { if (lower.startsWith("!watch ")) {
await handleWatch(message, content.slice(7).trim()); await handleWatch(message, content.slice(7).trim());
} else if (lower.startsWith("!remove ")) { } else if (lower.startsWith("!remove ")) {
@@ -1619,6 +1765,10 @@ client.on("messageCreate", async (message) => {
await handleList(message); await handleList(message);
} else if (lower === "!shops") { } else if (lower === "!shops") {
await handleShops(message); await handleShops(message);
} else if (lower === "!trivia") {
await handleTrivia(message);
} else if (lower === "!score") {
await handleScore(message);
} else if (lower === "!help") { } else if (lower === "!help") {
await handleHelp(message); await handleHelp(message);
} else if (!content.startsWith("!") && AI_ENABLED && } else if (!content.startsWith("!") && AI_ENABLED &&
+5 -1
View File
@@ -79,6 +79,10 @@ if (-not (Test-Path "memory.yaml")) {
Copy-Item "memory.yaml.example" "memory.yaml" Copy-Item "memory.yaml.example" "memory.yaml"
} }
if (-not (Test-Path "scores.json")) {
Copy-Item "scores.json.example" "scores.json"
}
if ([string]::IsNullOrWhiteSpace((Get-EnvValue "DISCORD_BOT_TOKEN"))) { if ([string]::IsNullOrWhiteSpace((Get-EnvValue "DISCORD_BOT_TOKEN"))) {
Write-Host "" Write-Host ""
Write-Host "Need a Discord bot token:" Write-Host "Need a Discord bot token:"
@@ -166,7 +170,7 @@ if (-not (Test-CommandExists pm2)) {
npm install -g pm2 npm install -g pm2
} }
$AppName = "discord-game-price-bot" $AppName = "discord-bot"
pm2 delete $AppName 2>$null | Out-Null pm2 delete $AppName 2>$null | Out-Null
pm2 start index.js --name $AppName --cwd $ScriptDir --output "$ScriptDir\bot.log" --error "$ScriptDir\bot.error.log" pm2 start index.js --name $AppName --cwd $ScriptDir --output "$ScriptDir\bot.log" --error "$ScriptDir\bot.error.log"
+5 -1
View File
@@ -82,6 +82,10 @@ if [[ ! -f memory.yaml ]]; then
cp memory.yaml.example memory.yaml cp memory.yaml.example memory.yaml
fi fi
if [[ ! -f scores.json ]]; then
cp scores.json.example scores.json
fi
CURRENT_TOKEN=$(grep -E "^DISCORD_BOT_TOKEN=" .env | cut -d "=" -f2-) CURRENT_TOKEN=$(grep -E "^DISCORD_BOT_TOKEN=" .env | cut -d "=" -f2-)
CURRENT_KEY=$(grep -E "^ITAD_API_KEY=" .env | cut -d "=" -f2-) CURRENT_KEY=$(grep -E "^ITAD_API_KEY=" .env | cut -d "=" -f2-)
@@ -170,7 +174,7 @@ if ! command -v pm2 >/dev/null 2>&1; then
npm install -g pm2 npm install -g pm2
fi fi
APP_NAME="discord-game-price-bot" APP_NAME="discord-bot"
pm2 delete "$APP_NAME" >/dev/null 2>&1 || true pm2 delete "$APP_NAME" >/dev/null 2>&1 || true
pm2 start index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --output "$SCRIPT_DIR/bot.log" --error "$SCRIPT_DIR/bot.error.log" pm2 start index.js --name "$APP_NAME" --cwd "$SCRIPT_DIR" --output "$SCRIPT_DIR/bot.log" --error "$SCRIPT_DIR/bot.error.log"