From 516600b88be1fac33775f3a596535d8e1a6939aa Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Thu, 3 Jul 2025 20:11:22 +0530 Subject: [PATCH 1/7] updated blog post link to solve #28 --- src/features/commands.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index afc0659f..ec23d9ef 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -1260,7 +1260,7 @@ Authentication is a critical part of most web applications. Here are some resour title: "create-react-app is deprecated", type: EmbedType.Rich, description: ` -create-react-app is deprecated and no longer recommended for use. It is not maintained and has a large number of security vulnerabilities. Please use [Vite](https://vitejs.dev/) or [Next.js](https://nextjs.org/) instead. The [React docs](https://react.dev/learn/start-a-new-react-project#can-i-use-react-without-a-framework) has more to say about using React without the use of a framework like Next or Remix. +create-react-app is deprecated and no longer recommended for use. It is not maintained and has a large number of security vulnerabilities. Please use [Vite](https://vitejs.dev/) or [Next.js](https://nextjs.org/) instead. The [React docs](https://react.dev/blog/2025/02/14/sunsetting-create-react-app) has more to say about using React without the use of a framework like Next or Remix. `, color: EMBED_COLOR, }, From af67627ec6804d3929e88f499775aab9c38cb682 Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Fri, 4 Jul 2025 13:17:07 +0530 Subject: [PATCH 2/7] added courses command --- src/features/commands.ts | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index 01d2ed4c..aad499d4 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -176,6 +176,44 @@ If you’re looking to gain experience in open source, we recommend starting wit }); }, }, + { + words: [`!courses`], + help: `provides a curated list of quality React courses and learning platforms`, + category: "React/Redux", + handleMessage: (msg) => { + msg.channel.send({ + embeds: [ + { + title: "Quality React Courses & Learning Platforms", + type: EmbedType.Rich, + description: `Here are some highly recommended courses and platforms for learning React, from beginner to advanced levels:`, + fields: [ + { + name: "Premium Courses", + value: ` +- [Joy of React](https://www.joyofreact.com/) - Comprehensive React course +- [Epic React](https://epicreact.dev/) - Advanced React patterns and techniques +- [Advanced React](https://www.advanced-react.com/) - By Nadia Makarevich +- [Jack Herrington's Courses](https://www.youtube.com/@jherr) - Various React topics + `, + inline: true, + }, + { + name: "Learning Platforms", + value: ` +- [Frontend Masters](https://frontendmasters.com/) - Professional-grade courses +- [React.dev](https://react.dev/) - Official React documentation +- [Reactiflux Learning Resources](https://www.reactiflux.com/learning) - Curated links + `, + inline: true, + }, + ], + color: EMBED_COLOR, + }, + ], + }); + }, + }, { words: [`!ymnnr`], help: `links to the You Might Not Need Redux article`, @@ -186,7 +224,7 @@ If you’re looking to gain experience in open source, we recommend starting wit { title: "You Might Not Need Redux", type: EmbedType.Rich, - description: `People often choose Redux before they need it. “What if our app doesn’t scale without it?" + description: `People often choose Redux before they need it. “What if our app doesn't scale without it?" Read more about this in the [article "You Might Not Need Redux" by Dan Abramov](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367)`, color: EMBED_COLOR, From 7d2702617433c967a5192284715f7782265b8a4a Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Wed, 1 Oct 2025 23:07:22 +0530 Subject: [PATCH 3/7] Command inside Code Block - #412 --- src/features/commands.ts | 56 +++++- test/commands.test.ts | 390 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 445 insertions(+), 1 deletion(-) create mode 100644 test/commands.test.ts diff --git a/src/features/commands.ts b/src/features/commands.ts index 73d7b77e..f520aa2c 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -1311,6 +1311,60 @@ create-react-app is deprecated and no longer recommended for use. It is not main }, ]; +// Escapes characters in a string that have special meaning in regular expressions. +const escapeRegex = (string: string): string => { + return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +}; + +/** + * Checks if a command word exists in a string, ignoring any commands + * found inside Markdown code blocks (both single ` and triple ```). + */ + +export const shouldTriggerCommand = ( + content: string, + commandWord: string, +): boolean => { + // A command word must exist to trigger a command. + if (!commandWord) { + return false; + } + + // 1. Sanitize the content by replacing escaped backticks with placeholders. + const sanitizedContent = content + .replace(/\\```/g, "\uE001") // Placeholder for escaped ``` + .replace(/\\`/g, "\uE000"); // Placeholder for escaped ` + + // 2. Isolate content outside of multi-line code blocks. + // By splitting by ```, the even-indexed parts of the array are the sections + // of text that are *outside* the code blocks. + const partsOutsideTripleBackticks = sanitizedContent + .split("```") + .filter((_, i) => i % 2 === 0); + + // 3. From the remaining parts, isolate content outside of inline code blocks. + // We do the same process for single backticks on each of the remaining parts. + const partsOutsideAllBackticks = partsOutsideTripleBackticks.flatMap((part) => + part.split("`").filter((_, i) => i % 2 === 0), + ); + + // 4. Rebuild the string with all code block content now removed. + const processedContent = partsOutsideAllBackticks.join(""); + + // 5. Restore the literal backticks from the placeholders. + const finalContent = processedContent + .replace(/\uE001/g, "```") + .replace(/\uE000/g, "`"); + + // 6. Create a regular expression to find the command as a "whole word" + const commandRegex = new RegExp( + `(? { const groupedMessages: { [key in Categories]: Command[] } = { Reactiflux: [], @@ -1368,7 +1422,7 @@ const commands: ChannelHandlers = { commandsList.forEach((command) => { const keyword = command.words.find((word) => { - return msg.content.toLowerCase().includes(word); + return shouldTriggerCommand(msg.content, word); }); if (keyword) { diff --git a/test/commands.test.ts b/test/commands.test.ts new file mode 100644 index 00000000..7748e76f --- /dev/null +++ b/test/commands.test.ts @@ -0,0 +1,390 @@ +import { describe, it, expect } from "vitest"; +import { shouldTriggerCommand } from "../src/features/commands.js"; + +describe("shouldTriggerCommand", () => { + describe("Basic command detection", () => { + it("should trigger for exact command match", () => { + expect(shouldTriggerCommand("!commands", "!commands")).toBe(true); + }); + + it("should trigger for command at start of message", () => { + expect(shouldTriggerCommand("!commands me please", "!commands")).toBe( + true, + ); + }); + + it("should trigger for command at end of message", () => { + expect( + shouldTriggerCommand("Please show me !commands", "!commands"), + ).toBe(true); + }); + + it("should trigger for command in middle of message", () => { + expect( + shouldTriggerCommand("Can you !commands me with this?", "!commands"), + ).toBe(true); + }); + + it("should be case insensitive", () => { + expect(shouldTriggerCommand("!COMMANDS", "!commands")).toBe(true); + expect(shouldTriggerCommand("!Commands", "!commands")).toBe(true); + expect(shouldTriggerCommand("!commands", "!COMMANDS")).toBe(true); + }); + }); + + describe("Word boundary detection", () => { + it("should not trigger for partial matches", () => { + expect(shouldTriggerCommand("helping", "docs")).toBe(false); + expect(shouldTriggerCommand("!docsful", "!docs")).toBe(false); + expect(shouldTriggerCommand("undocsful", "docs")).toBe(false); + }); + + it("should trigger with word boundaries", () => { + expect(shouldTriggerCommand("docs me", "docs")).toBe(true); + expect(shouldTriggerCommand("need docs now", "docs")).toBe(true); + expect(shouldTriggerCommand("docs!", "docs")).toBe(true); + expect(shouldTriggerCommand("docs?", "docs")).toBe(true); + expect(shouldTriggerCommand("docs.", "docs")).toBe(true); + }); + + it("should handle punctuation correctly", () => { + expect(shouldTriggerCommand("!docs!", "!docs")).toBe(true); + expect(shouldTriggerCommand("!docs?", "!docs")).toBe(true); + expect(shouldTriggerCommand("!docs.", "!docs")).toBe(true); + expect(shouldTriggerCommand("(!docs)", "!docs")).toBe(true); + expect(shouldTriggerCommand("[!docs]", "!docs")).toBe(true); + expect(shouldTriggerCommand("{!docs}", "!docs")).toBe(true); + }); + }); + + describe("Single backtick code blocks (inline)", () => { + it("should NOT trigger inside single backticks", () => { + expect(shouldTriggerCommand("`!docs`", "!docs")).toBe(false); + expect(shouldTriggerCommand("Use `!docs` command", "!docs")).toBe(false); + expect(shouldTriggerCommand("Try `!docs` or `!commands`", "!docs")).toBe( + false, + ); + }); + + it("should trigger outside single backticks", () => { + expect(shouldTriggerCommand("`code` !docs", "!docs")).toBe(true); + expect(shouldTriggerCommand("!docs `code`", "!docs")).toBe(true); + expect(shouldTriggerCommand("`code` !docs `more code`", "!docs")).toBe( + true, + ); + }); + + it("should handle multiple inline code blocks correctly", () => { + expect(shouldTriggerCommand("`code1` `code2` !docs", "!docs")).toBe(true); + expect(shouldTriggerCommand("`!other` text !docs", "!docs")).toBe(true); + expect(shouldTriggerCommand("`!docs` and `!commands`", "!docs")).toBe( + false, + ); + }); + + it("should handle unmatched single backticks", () => { + expect(shouldTriggerCommand("`unclosed !docs", "!docs")).toBe(false); + expect(shouldTriggerCommand("unclosed` !docs", "!docs")).toBe(false); + expect(shouldTriggerCommand("`!docs unclosed", "!docs")).toBe(false); + }); + }); + + describe("Triple backtick code blocks (multiline)", () => { + it("should NOT trigger inside triple backticks", () => { + expect(shouldTriggerCommand("```\n!docs\n```", "!docs")).toBe(false); + expect(shouldTriggerCommand("```js\n!docs\n```", "!docs")).toBe(false); + expect(shouldTriggerCommand("```\n!docs me\n```", "!docs")).toBe(false); + }); + + it("should trigger outside triple backticks", () => { + expect(shouldTriggerCommand("```\ncode\n``` !docs", "!docs")).toBe(true); + expect(shouldTriggerCommand("!docs ```\ncode\n```", "!docs")).toBe(true); + expect( + shouldTriggerCommand("```\ncode\n``` !docs ```\nmore\n```", "!docs"), + ).toBe(true); + }); + + it("should handle language specifiers", () => { + expect(shouldTriggerCommand("```javascript\n!docs\n```", "!docs")).toBe( + false, + ); + expect(shouldTriggerCommand("```typescript\n!docs\n```", "!docs")).toBe( + false, + ); + expect(shouldTriggerCommand("```python\n!docs\n```", "!docs")).toBe( + false, + ); + }); + + it("should handle unmatched triple backticks", () => { + expect(shouldTriggerCommand("```\n!docs", "!docs")).toBe(false); + expect(shouldTriggerCommand("!docs\n```", "!docs")).toBe(true); + expect(shouldTriggerCommand("```\n!docs\n``", "!docs")).toBe(false); + }); + }); + + describe("Mixed code block scenarios", () => { + it("should handle both single and triple backticks correctly", () => { + expect( + shouldTriggerCommand("```\ncode\n``` `inline` !docs", "!docs"), + ).toBe(true); + expect( + shouldTriggerCommand("```\n!docs\n``` `also !docs`", "!docs"), + ).toBe(false); + expect( + shouldTriggerCommand("`inline !docs` ```\nblock\n```", "!docs"), + ).toBe(false); + }); + + it("should handle nested-like scenarios", () => { + expect(shouldTriggerCommand("```\n`!docs`\n```", "!docs")).toBe(false); + expect(shouldTriggerCommand("`code ```\n!docs\n```", "!docs")).toBe( + false, + ); + }); + + it("should handle multiple code blocks with commands between", () => { + expect( + shouldTriggerCommand("```\ncode1\n``` !docs ```\ncode2\n```", "!docs"), + ).toBe(true); + expect(shouldTriggerCommand("`code1` !docs `code2`", "!docs")).toBe(true); + }); + }); + + describe("Edge cases with whitespace and newlines", () => { + it("should handle commands with surrounding whitespace", () => { + expect(shouldTriggerCommand(" !docs ", "!docs")).toBe(true); + expect(shouldTriggerCommand("\n!docs\n", "!docs")).toBe(true); + expect(shouldTriggerCommand("\t!docs\t", "!docs")).toBe(true); + }); + + it("should handle multiline messages", () => { + expect(shouldTriggerCommand("line1\n!docs\nline3", "!docs")).toBe(true); + expect( + shouldTriggerCommand("```\nline1\n!docs\nline3\n```", "!docs"), + ).toBe(false); + }); + + it("should handle mixed whitespace in code blocks", () => { + expect(shouldTriggerCommand("``` \n !docs \n ```", "!docs")).toBe(false); + expect(shouldTriggerCommand("` !docs `", "!docs")).toBe(false); + }); + }); + + describe("Complex real-world scenarios", () => { + it("should handle typical Discord message with code", () => { + const message = `Here's my code: +\`\`\`javascript +function test() { + // !docs this doesn't work + console.log("!commands"); +} +\`\`\` +Can someone !ask me with this?`; + expect(shouldTriggerCommand(message, "!ask")).toBe(true); + expect(shouldTriggerCommand(message, "!docs")).toBe(false); + expect(shouldTriggerCommand(message, "!commands")).toBe(false); + }); + + it("should handle inline code with explanation", () => { + const message = `Try using \`!docs\` command or check the !commands`; + expect(shouldTriggerCommand(message, "!docs")).toBe(false); + expect(shouldTriggerCommand(message, "!commands")).toBe(true); + }); + + it("should handle long messages with multiple code blocks", () => { + const message = ` +First, try this: +\`\`\`js +// Don't use !ask here +console.log("test"); +\`\`\` + +Then check \`!commands\` list. + +Finally, use !docs if needed. + +\`\`\` +// Another block with !mdn +\`\`\` +`; + expect(shouldTriggerCommand(message, "!docs")).toBe(true); + expect(shouldTriggerCommand(message, "!commands")).toBe(false); + expect(shouldTriggerCommand(message, "!ask")).toBe(false); + expect(shouldTriggerCommand(message, "!mdn")).toBe(false); + }); + + it("should handle escaped backticks", () => { + expect(shouldTriggerCommand("\\`!docs\\`", "!docs")).toBe(true); + expect(shouldTriggerCommand("\\`\\`\\`\n!docs\n\\`\\`\\`", "!docs")).toBe( + true, + ); + }); + }); + + describe("Performance edge cases", () => { + it("should handle very long messages", () => { + const longText = "a".repeat(10000); + const message = `${longText} !docs ${longText}`; + expect(shouldTriggerCommand(message, "!docs")).toBe(true); + }); + + it("should handle messages with many code blocks", () => { + let message = ""; + for (let i = 0; i < 100; i++) { + message += `\`code${i}\` `; + } + message += "!docs"; + expect(shouldTriggerCommand(message, "!docs")).toBe(true); + }); + + it("should handle large code blocks", () => { + const largeCode = 'console.log("test");\n'.repeat(1000); + const message = `\`\`\`js\n${largeCode}!docs\n\`\`\``; + expect(shouldTriggerCommand(message, "!docs")).toBe(false); + }); + }); + + describe("Special characters and Unicode", () => { + it("should handle Unicode characters", () => { + expect(shouldTriggerCommand("🚀 !docs 🎉", "!docs")).toBe(true); + expect(shouldTriggerCommand("`🚀 !docs 🎉`", "!docs")).toBe(false); + }); + + it("should handle special regex characters in command", () => { + expect(shouldTriggerCommand("Use !test.+ command", "!test.+")).toBe(true); + expect(shouldTriggerCommand("Use !test* command", "!test*")).toBe(true); + expect(shouldTriggerCommand("Use !test? command", "!test?")).toBe(true); + }); + + it("should handle empty strings", () => { + expect(shouldTriggerCommand("", "!docs")).toBe(false); + expect(shouldTriggerCommand("!docs", "")).toBe(false); + expect(shouldTriggerCommand("", "")).toBe(false); + }); + }); + + describe("Actual command examples from codebase", () => { + it("should work with real command examples", () => { + expect(shouldTriggerCommand("!commands", "!commands")).toBe(true); + expect(shouldTriggerCommand("!conduct", "!conduct")).toBe(true); + expect(shouldTriggerCommand("!mdn Array.prototype.map", "!mdn")).toBe( + true, + ); + expect(shouldTriggerCommand("!react-docs useState", "!react-docs")).toBe( + true, + ); + expect(shouldTriggerCommand("!docs useState", "!docs")).toBe(true); + expect(shouldTriggerCommand("!ask", "!ask")).toBe(true); + expect(shouldTriggerCommand("!code", "!code")).toBe(true); + expect(shouldTriggerCommand("!gist", "!gist")).toBe(true); + expect(shouldTriggerCommand("!xy", "!xy")).toBe(true); + }); + + it("should not trigger for commands in code examples", () => { + const codeExample = `\`\`\`javascript +// Don't actually run !commands here +console.log("Use !ask for assistance"); +\`\`\``; + expect(shouldTriggerCommand(codeExample, "!commands")).toBe(false); + expect(shouldTriggerCommand(codeExample, "!ask")).toBe(false); + }); + }); + + // Add this new describe block to your existing test file + describe("Two-pointer backtick removal algorithm", () => { + it("should handle nested backticks correctly", () => { + expect(shouldTriggerCommand("```\n`!docs`\n```", "!docs")).toBe(false); + expect(shouldTriggerCommand("`code ```\n!docs\n```", "!docs")).toBe( + false, + ); + }); + + it("should handle multiple separate code blocks efficiently", () => { + expect( + shouldTriggerCommand("`code1` !docs `code2` !ask `code3`", "!docs"), + ).toBe(true); + expect( + shouldTriggerCommand("`code1` !docs `code2` !ask `code3`", "!ask"), + ).toBe(true); + expect( + shouldTriggerCommand( + "```\nblock1\n``` !docs ```\nblock2\n``` !ask", + "!docs", + ), + ).toBe(true); + }); + + it("should handle alternating backticks like palindrome logic", () => { + expect(shouldTriggerCommand("`outer```inner```outer`", "!docs")).toBe( + false, + ); // No command, but tests the algorithm + expect(shouldTriggerCommand("```outer`inner`outer```", "!docs")).toBe( + false, + ); // No command, but tests the algorithm + expect(shouldTriggerCommand("`!docs```!ask```!help`", "!docs")).toBe( + false, + ); + expect(shouldTriggerCommand("`!docs```!ask```!help`", "!ask")).toBe( + false, + ); + }); + + it("should efficiently process large content with many backticks", () => { + let content = ""; + for (let i = 0; i < 50; i++) { + content += `\`code${i}\` `; + } + content += "!docs "; + for (let i = 50; i < 100; i++) { + content += `\`\`\`\nblock${i}\n\`\`\` `; + } + + expect(shouldTriggerCommand(content, "!docs")).toBe(true); + }); + }); + + describe("Interaction with Discord-specific syntax", () => { + it("should trigger when next to a user mention", () => { + expect( + shouldTriggerCommand("hey <@123456789012345678>, use !docs", "!docs"), + ).toBe(true); + expect(shouldTriggerCommand("!docs <@123456789012345678>", "!docs")).toBe( + true, + ); + }); + + it("should not trigger if inside a mention-like text", () => { + // This is an unlikely edge case, but good for completeness + expect(shouldTriggerCommand("some text", "!docs")).toBe(false); + }); + }); + + describe("Commands within other text structures", () => { + it("should NOT trigger when the command word is part of a URL", () => { + expect( + shouldTriggerCommand( + "Check out https://example.com/!docs/guide", + "!docs", + ), + ).toBe(false); + expect( + shouldTriggerCommand("Another link:https://somedocs.com", "docs"), + ).toBe(false); + }); + }); + + describe("Commands with adjacent non-alphanumeric characters", () => { + it("should trigger when immediately followed by an emoji", () => { + expect(shouldTriggerCommand("!docs🚀", "!docs")).toBe(true); + expect(shouldTriggerCommand("Can I get !help🙏", "!help")).toBe(true); + }); + }); + + describe("Commands with adjacent non-alphanumeric characters", () => { + it("should trigger when immediately followed by an emoji", () => { + expect(shouldTriggerCommand("!docs🚀", "!docs")).toBe(true); + expect(shouldTriggerCommand("Can I get !help🙏", "!help")).toBe(true); + }); + }); +}); From ec460bbf0d5092dff592117d7908cd80df7c9ac1 Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Wed, 1 Oct 2025 23:10:17 +0530 Subject: [PATCH 4/7] removed test file --- test/commands.test.ts | 390 ------------------------------------------ 1 file changed, 390 deletions(-) delete mode 100644 test/commands.test.ts diff --git a/test/commands.test.ts b/test/commands.test.ts deleted file mode 100644 index 7748e76f..00000000 --- a/test/commands.test.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { describe, it, expect } from "vitest"; -import { shouldTriggerCommand } from "../src/features/commands.js"; - -describe("shouldTriggerCommand", () => { - describe("Basic command detection", () => { - it("should trigger for exact command match", () => { - expect(shouldTriggerCommand("!commands", "!commands")).toBe(true); - }); - - it("should trigger for command at start of message", () => { - expect(shouldTriggerCommand("!commands me please", "!commands")).toBe( - true, - ); - }); - - it("should trigger for command at end of message", () => { - expect( - shouldTriggerCommand("Please show me !commands", "!commands"), - ).toBe(true); - }); - - it("should trigger for command in middle of message", () => { - expect( - shouldTriggerCommand("Can you !commands me with this?", "!commands"), - ).toBe(true); - }); - - it("should be case insensitive", () => { - expect(shouldTriggerCommand("!COMMANDS", "!commands")).toBe(true); - expect(shouldTriggerCommand("!Commands", "!commands")).toBe(true); - expect(shouldTriggerCommand("!commands", "!COMMANDS")).toBe(true); - }); - }); - - describe("Word boundary detection", () => { - it("should not trigger for partial matches", () => { - expect(shouldTriggerCommand("helping", "docs")).toBe(false); - expect(shouldTriggerCommand("!docsful", "!docs")).toBe(false); - expect(shouldTriggerCommand("undocsful", "docs")).toBe(false); - }); - - it("should trigger with word boundaries", () => { - expect(shouldTriggerCommand("docs me", "docs")).toBe(true); - expect(shouldTriggerCommand("need docs now", "docs")).toBe(true); - expect(shouldTriggerCommand("docs!", "docs")).toBe(true); - expect(shouldTriggerCommand("docs?", "docs")).toBe(true); - expect(shouldTriggerCommand("docs.", "docs")).toBe(true); - }); - - it("should handle punctuation correctly", () => { - expect(shouldTriggerCommand("!docs!", "!docs")).toBe(true); - expect(shouldTriggerCommand("!docs?", "!docs")).toBe(true); - expect(shouldTriggerCommand("!docs.", "!docs")).toBe(true); - expect(shouldTriggerCommand("(!docs)", "!docs")).toBe(true); - expect(shouldTriggerCommand("[!docs]", "!docs")).toBe(true); - expect(shouldTriggerCommand("{!docs}", "!docs")).toBe(true); - }); - }); - - describe("Single backtick code blocks (inline)", () => { - it("should NOT trigger inside single backticks", () => { - expect(shouldTriggerCommand("`!docs`", "!docs")).toBe(false); - expect(shouldTriggerCommand("Use `!docs` command", "!docs")).toBe(false); - expect(shouldTriggerCommand("Try `!docs` or `!commands`", "!docs")).toBe( - false, - ); - }); - - it("should trigger outside single backticks", () => { - expect(shouldTriggerCommand("`code` !docs", "!docs")).toBe(true); - expect(shouldTriggerCommand("!docs `code`", "!docs")).toBe(true); - expect(shouldTriggerCommand("`code` !docs `more code`", "!docs")).toBe( - true, - ); - }); - - it("should handle multiple inline code blocks correctly", () => { - expect(shouldTriggerCommand("`code1` `code2` !docs", "!docs")).toBe(true); - expect(shouldTriggerCommand("`!other` text !docs", "!docs")).toBe(true); - expect(shouldTriggerCommand("`!docs` and `!commands`", "!docs")).toBe( - false, - ); - }); - - it("should handle unmatched single backticks", () => { - expect(shouldTriggerCommand("`unclosed !docs", "!docs")).toBe(false); - expect(shouldTriggerCommand("unclosed` !docs", "!docs")).toBe(false); - expect(shouldTriggerCommand("`!docs unclosed", "!docs")).toBe(false); - }); - }); - - describe("Triple backtick code blocks (multiline)", () => { - it("should NOT trigger inside triple backticks", () => { - expect(shouldTriggerCommand("```\n!docs\n```", "!docs")).toBe(false); - expect(shouldTriggerCommand("```js\n!docs\n```", "!docs")).toBe(false); - expect(shouldTriggerCommand("```\n!docs me\n```", "!docs")).toBe(false); - }); - - it("should trigger outside triple backticks", () => { - expect(shouldTriggerCommand("```\ncode\n``` !docs", "!docs")).toBe(true); - expect(shouldTriggerCommand("!docs ```\ncode\n```", "!docs")).toBe(true); - expect( - shouldTriggerCommand("```\ncode\n``` !docs ```\nmore\n```", "!docs"), - ).toBe(true); - }); - - it("should handle language specifiers", () => { - expect(shouldTriggerCommand("```javascript\n!docs\n```", "!docs")).toBe( - false, - ); - expect(shouldTriggerCommand("```typescript\n!docs\n```", "!docs")).toBe( - false, - ); - expect(shouldTriggerCommand("```python\n!docs\n```", "!docs")).toBe( - false, - ); - }); - - it("should handle unmatched triple backticks", () => { - expect(shouldTriggerCommand("```\n!docs", "!docs")).toBe(false); - expect(shouldTriggerCommand("!docs\n```", "!docs")).toBe(true); - expect(shouldTriggerCommand("```\n!docs\n``", "!docs")).toBe(false); - }); - }); - - describe("Mixed code block scenarios", () => { - it("should handle both single and triple backticks correctly", () => { - expect( - shouldTriggerCommand("```\ncode\n``` `inline` !docs", "!docs"), - ).toBe(true); - expect( - shouldTriggerCommand("```\n!docs\n``` `also !docs`", "!docs"), - ).toBe(false); - expect( - shouldTriggerCommand("`inline !docs` ```\nblock\n```", "!docs"), - ).toBe(false); - }); - - it("should handle nested-like scenarios", () => { - expect(shouldTriggerCommand("```\n`!docs`\n```", "!docs")).toBe(false); - expect(shouldTriggerCommand("`code ```\n!docs\n```", "!docs")).toBe( - false, - ); - }); - - it("should handle multiple code blocks with commands between", () => { - expect( - shouldTriggerCommand("```\ncode1\n``` !docs ```\ncode2\n```", "!docs"), - ).toBe(true); - expect(shouldTriggerCommand("`code1` !docs `code2`", "!docs")).toBe(true); - }); - }); - - describe("Edge cases with whitespace and newlines", () => { - it("should handle commands with surrounding whitespace", () => { - expect(shouldTriggerCommand(" !docs ", "!docs")).toBe(true); - expect(shouldTriggerCommand("\n!docs\n", "!docs")).toBe(true); - expect(shouldTriggerCommand("\t!docs\t", "!docs")).toBe(true); - }); - - it("should handle multiline messages", () => { - expect(shouldTriggerCommand("line1\n!docs\nline3", "!docs")).toBe(true); - expect( - shouldTriggerCommand("```\nline1\n!docs\nline3\n```", "!docs"), - ).toBe(false); - }); - - it("should handle mixed whitespace in code blocks", () => { - expect(shouldTriggerCommand("``` \n !docs \n ```", "!docs")).toBe(false); - expect(shouldTriggerCommand("` !docs `", "!docs")).toBe(false); - }); - }); - - describe("Complex real-world scenarios", () => { - it("should handle typical Discord message with code", () => { - const message = `Here's my code: -\`\`\`javascript -function test() { - // !docs this doesn't work - console.log("!commands"); -} -\`\`\` -Can someone !ask me with this?`; - expect(shouldTriggerCommand(message, "!ask")).toBe(true); - expect(shouldTriggerCommand(message, "!docs")).toBe(false); - expect(shouldTriggerCommand(message, "!commands")).toBe(false); - }); - - it("should handle inline code with explanation", () => { - const message = `Try using \`!docs\` command or check the !commands`; - expect(shouldTriggerCommand(message, "!docs")).toBe(false); - expect(shouldTriggerCommand(message, "!commands")).toBe(true); - }); - - it("should handle long messages with multiple code blocks", () => { - const message = ` -First, try this: -\`\`\`js -// Don't use !ask here -console.log("test"); -\`\`\` - -Then check \`!commands\` list. - -Finally, use !docs if needed. - -\`\`\` -// Another block with !mdn -\`\`\` -`; - expect(shouldTriggerCommand(message, "!docs")).toBe(true); - expect(shouldTriggerCommand(message, "!commands")).toBe(false); - expect(shouldTriggerCommand(message, "!ask")).toBe(false); - expect(shouldTriggerCommand(message, "!mdn")).toBe(false); - }); - - it("should handle escaped backticks", () => { - expect(shouldTriggerCommand("\\`!docs\\`", "!docs")).toBe(true); - expect(shouldTriggerCommand("\\`\\`\\`\n!docs\n\\`\\`\\`", "!docs")).toBe( - true, - ); - }); - }); - - describe("Performance edge cases", () => { - it("should handle very long messages", () => { - const longText = "a".repeat(10000); - const message = `${longText} !docs ${longText}`; - expect(shouldTriggerCommand(message, "!docs")).toBe(true); - }); - - it("should handle messages with many code blocks", () => { - let message = ""; - for (let i = 0; i < 100; i++) { - message += `\`code${i}\` `; - } - message += "!docs"; - expect(shouldTriggerCommand(message, "!docs")).toBe(true); - }); - - it("should handle large code blocks", () => { - const largeCode = 'console.log("test");\n'.repeat(1000); - const message = `\`\`\`js\n${largeCode}!docs\n\`\`\``; - expect(shouldTriggerCommand(message, "!docs")).toBe(false); - }); - }); - - describe("Special characters and Unicode", () => { - it("should handle Unicode characters", () => { - expect(shouldTriggerCommand("🚀 !docs 🎉", "!docs")).toBe(true); - expect(shouldTriggerCommand("`🚀 !docs 🎉`", "!docs")).toBe(false); - }); - - it("should handle special regex characters in command", () => { - expect(shouldTriggerCommand("Use !test.+ command", "!test.+")).toBe(true); - expect(shouldTriggerCommand("Use !test* command", "!test*")).toBe(true); - expect(shouldTriggerCommand("Use !test? command", "!test?")).toBe(true); - }); - - it("should handle empty strings", () => { - expect(shouldTriggerCommand("", "!docs")).toBe(false); - expect(shouldTriggerCommand("!docs", "")).toBe(false); - expect(shouldTriggerCommand("", "")).toBe(false); - }); - }); - - describe("Actual command examples from codebase", () => { - it("should work with real command examples", () => { - expect(shouldTriggerCommand("!commands", "!commands")).toBe(true); - expect(shouldTriggerCommand("!conduct", "!conduct")).toBe(true); - expect(shouldTriggerCommand("!mdn Array.prototype.map", "!mdn")).toBe( - true, - ); - expect(shouldTriggerCommand("!react-docs useState", "!react-docs")).toBe( - true, - ); - expect(shouldTriggerCommand("!docs useState", "!docs")).toBe(true); - expect(shouldTriggerCommand("!ask", "!ask")).toBe(true); - expect(shouldTriggerCommand("!code", "!code")).toBe(true); - expect(shouldTriggerCommand("!gist", "!gist")).toBe(true); - expect(shouldTriggerCommand("!xy", "!xy")).toBe(true); - }); - - it("should not trigger for commands in code examples", () => { - const codeExample = `\`\`\`javascript -// Don't actually run !commands here -console.log("Use !ask for assistance"); -\`\`\``; - expect(shouldTriggerCommand(codeExample, "!commands")).toBe(false); - expect(shouldTriggerCommand(codeExample, "!ask")).toBe(false); - }); - }); - - // Add this new describe block to your existing test file - describe("Two-pointer backtick removal algorithm", () => { - it("should handle nested backticks correctly", () => { - expect(shouldTriggerCommand("```\n`!docs`\n```", "!docs")).toBe(false); - expect(shouldTriggerCommand("`code ```\n!docs\n```", "!docs")).toBe( - false, - ); - }); - - it("should handle multiple separate code blocks efficiently", () => { - expect( - shouldTriggerCommand("`code1` !docs `code2` !ask `code3`", "!docs"), - ).toBe(true); - expect( - shouldTriggerCommand("`code1` !docs `code2` !ask `code3`", "!ask"), - ).toBe(true); - expect( - shouldTriggerCommand( - "```\nblock1\n``` !docs ```\nblock2\n``` !ask", - "!docs", - ), - ).toBe(true); - }); - - it("should handle alternating backticks like palindrome logic", () => { - expect(shouldTriggerCommand("`outer```inner```outer`", "!docs")).toBe( - false, - ); // No command, but tests the algorithm - expect(shouldTriggerCommand("```outer`inner`outer```", "!docs")).toBe( - false, - ); // No command, but tests the algorithm - expect(shouldTriggerCommand("`!docs```!ask```!help`", "!docs")).toBe( - false, - ); - expect(shouldTriggerCommand("`!docs```!ask```!help`", "!ask")).toBe( - false, - ); - }); - - it("should efficiently process large content with many backticks", () => { - let content = ""; - for (let i = 0; i < 50; i++) { - content += `\`code${i}\` `; - } - content += "!docs "; - for (let i = 50; i < 100; i++) { - content += `\`\`\`\nblock${i}\n\`\`\` `; - } - - expect(shouldTriggerCommand(content, "!docs")).toBe(true); - }); - }); - - describe("Interaction with Discord-specific syntax", () => { - it("should trigger when next to a user mention", () => { - expect( - shouldTriggerCommand("hey <@123456789012345678>, use !docs", "!docs"), - ).toBe(true); - expect(shouldTriggerCommand("!docs <@123456789012345678>", "!docs")).toBe( - true, - ); - }); - - it("should not trigger if inside a mention-like text", () => { - // This is an unlikely edge case, but good for completeness - expect(shouldTriggerCommand("some text", "!docs")).toBe(false); - }); - }); - - describe("Commands within other text structures", () => { - it("should NOT trigger when the command word is part of a URL", () => { - expect( - shouldTriggerCommand( - "Check out https://example.com/!docs/guide", - "!docs", - ), - ).toBe(false); - expect( - shouldTriggerCommand("Another link:https://somedocs.com", "docs"), - ).toBe(false); - }); - }); - - describe("Commands with adjacent non-alphanumeric characters", () => { - it("should trigger when immediately followed by an emoji", () => { - expect(shouldTriggerCommand("!docs🚀", "!docs")).toBe(true); - expect(shouldTriggerCommand("Can I get !help🙏", "!help")).toBe(true); - }); - }); - - describe("Commands with adjacent non-alphanumeric characters", () => { - it("should trigger when immediately followed by an emoji", () => { - expect(shouldTriggerCommand("!docs🚀", "!docs")).toBe(true); - expect(shouldTriggerCommand("Can I get !help🙏", "!help")).toBe(true); - }); - }); -}); From e739046f5912ccfb8b8f2b64be2546fe6c596d71 Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Thu, 2 Oct 2025 10:12:50 +0530 Subject: [PATCH 5/7] revised message handling logic --- src/features/commands.ts | 56 ++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/features/commands.ts b/src/features/commands.ts index f520aa2c..37a1bc45 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -1420,21 +1420,49 @@ const commands: ChannelHandlers = { return; } - commandsList.forEach((command) => { - const keyword = command.words.find((word) => { - return shouldTriggerCommand(msg.content, word); - }); + const { content } = msg; - if (keyword) { - if (cooldown.hasCooldown(msg.author.id, `commands.${keyword}`)) return; - cooldown.addCooldown( - msg.author.id, - `commands.${keyword}`, - command.cooldown, - ); - command.handleMessage(msg); - } - }); + // 1. CHEAP GUARD CLAUSE: If the message doesn't even contain a '!', + // it can't be a command. Exit immediately. This handles >99% of messages + // with virtually zero overhead. + if (!content.trim().startsWith("!")) { + return; + } + + // 2. EXTRACT POTENTIAL COMMAND: Get the first word, which is the only + // possible command trigger. This is also a very cheap operation. + const potentialCommandWord = content.trim().split(" ")[0]; + + // 3. FIND THE COMMAND OBJECT: Quickly find the corresponding command object + // from the list. This is a fast lookup. + const command = commandsList.find((c) => + c.words.includes(potentialCommandWord), + ); + + // If no command object matches the word, it's not a valid command. Exit. + if (!command) { + return; + } + + // 4. EXPENSIVE CHECK (LAST RESORT): Now, and ONLY NOW, we run our expensive + // check because we are highly confident this is an attempt to use a valid command. + // We confirm it's not inside a code block. + if (!shouldTriggerCommand(content, potentialCommandWord)) { + return; + } + + // 5. EXECUTE: All checks have passed. Apply cooldown and run the command. + if (cooldown.hasCooldown(msg.author.id, `commands.${potentialCommandWord}`)) { + return; + } + + cooldown.addCooldown( + msg.author.id, + `commands.${potentialCommandWord}`, + command.cooldown, + ); + + command.handleMessage(msg); }, }; From b9133bb483565b04f39952372f841b6521688dae Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Thu, 2 Oct 2025 10:18:47 +0530 Subject: [PATCH 6/7] formatted --- package.json | 2 +- src/features/commands.ts | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 472db64a..067e6828 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "start": "node dist/index.js", - "dev": "npm-run-all --parallel 'dev:*'", + "dev": "npm-run-all --parallel dev:bot dev:test", "dev:bot": "tsx --watch ./src/index.ts", "dev:test": "vitest", "build": "tsc -b", diff --git a/src/features/commands.ts b/src/features/commands.ts index 37a1bc45..0735144e 100644 --- a/src/features/commands.ts +++ b/src/features/commands.ts @@ -1452,16 +1452,18 @@ const commands: ChannelHandlers = { } // 5. EXECUTE: All checks have passed. Apply cooldown and run the command. - if (cooldown.hasCooldown(msg.author.id, `commands.${potentialCommandWord}`)) { + if ( + cooldown.hasCooldown(msg.author.id, `commands.${potentialCommandWord}`) + ) { return; } - + cooldown.addCooldown( msg.author.id, `commands.${potentialCommandWord}`, command.cooldown, ); - + command.handleMessage(msg); }, }; From 5fe43dba22dc73e62491acb8a2a03d492163f15b Mon Sep 17 00:00:00 2001 From: achyu-dev Date: Thu, 2 Oct 2025 10:21:03 +0530 Subject: [PATCH 7/7] revert package json change --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 067e6828..472db64a 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "type": "module", "scripts": { "start": "node dist/index.js", - "dev": "npm-run-all --parallel dev:bot dev:test", + "dev": "npm-run-all --parallel 'dev:*'", "dev:bot": "tsx --watch ./src/index.ts", "dev:test": "vitest", "build": "tsc -b",