Skip to content

feat(handler): Add background handler for /mention-each command#95

Open
Devashish08 wants to merge 19 commits intoRealDevSquad:developfrom
Devashish08:mention-each-handler
Open

feat(handler): Add background handler for /mention-each command#95
Devashish08 wants to merge 19 commits intoRealDevSquad:developfrom
Devashish08:mention-each-handler

Conversation

@Devashish08
Copy link
Contributor

@Devashish08 Devashish08 commented Apr 29, 2025

Date: 29/04/2025

Developer Name: @Devashish08


Issue Ticket Number

Closes #70

Description

Notes for Reviewers:

This PR implements the background handler logic for the /mention-each command within the commands/handlers package. This code executes asynchronously after being dequeued from RabbitMQ, performing the core tasks of fetching members and sending the appropriate mention/list messages back to Discord.

Builds Upon: #80
Related PR: #93

Testing:

  • Unit tests for this handler layer will follow in PR

Documentation Updated?

  • Yes
  • No

Under Feature Flag

  • Yes
  • No

Database Changes

  • Yes
  • No

Breaking Changes

  • Yes
  • No

Development Tested?

  • Yes
  • No

Screenshots

image

Test Coverage

Additional Notes

@coderabbitai
Copy link

coderabbitai bot commented Apr 29, 2025

Summary by CodeRabbit

  • New Features
    • Added a new "MentionEach" command to mention all users with a specific role, with options for custom messages and different mention modes.
    • Introduced advanced handling for mentioning users individually, listing users without pinging, and sending batch mentions.
  • Bug Fixes
    • Corrected typos in method names related to user ID retrieval.
  • Tests
    • Added comprehensive tests for member role utilities and mention formatting.
    • Enhanced test mocks for Discord session interactions.
  • Chores
    • Added a new dependency for testing utilities.
    • Improved code formatting and import order.

Walkthrough

This update introduces the "MentionEach" command to a Discord bot codebase. It adds the command definition, handler, and service logic to process requests for mentioning all users with a specified role, either collectively or individually, with optional custom messages and feature flags. Supporting utilities for member retrieval and mention formatting are implemented, alongside comprehensive unit tests. Several method signature corrections and mock implementations are included to facilitate testing and ensure interface consistency. Minor code cleanups, such as import reordering and typo fixes, are also present.

Changes

File(s) Change Summary
commands/handlers/main.go, service/main.go Added support for the "MentionEach" command in the main handler and service dispatchers.
commands/handlers/mentionEachHandler.go Introduced the mentionEachHandler method and CommandParams struct to handle the "MentionEach" command, including logic for role member extraction, message formatting, and different mention modes.
commands/main.go Added the "MentionEach" command definition with options for role, message, dev, dev_title, and ff_enabled flags.
commands/main/register.go, commands/register/register.go Fixed typo in method call from GetUerId() to GetUserId() in the command registration logic.
commands/main/resgister_test.go, commands/register/register_test.go Extended mockSession with new fields and methods for GuildMembers and ChannelMessageSend; fixed typo in GetUserId method; removed duplicate panic test.
config/config.go Reordered import statements; no logic changes.
dtos/commands.go Added MentionEach field to CommandNameTypes struct.
go.mod Added indirect dependency github.com/stretchr/objx v0.5.2.
models/discord.go Fixed GetUserId typo; added GuildMembers and ChannelMessageSend methods to SessionWrapper and SessionInterface.
models/discord_test.go Updated tests for method signature changes; added tests for new methods; removed external utils dependency.
service/mentionEachService.go Added MentionEachService method to process the command, validate input, handle feature flags, and enqueue tasks for asynchronous processing.
tests/mocks/discordSessionMock.go Added a new Discord session mock implementing relevant methods for testing.
utils/constants.go Grouped constants; added DISCORD_GUILD_MEMBER_API_LIMIT; updated CommandNames with MentionEach.
utils/membersUtils.go Added utilities for fetching users with a role, formatting mentions, and generating mention responses.
utils/membersUtils_test.go Added unit tests for member utilities, covering various scenarios and edge cases.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Discord
    participant Service
    participant Handler
    participant Utils

    User->>Discord: Issues /mention-each command
    Discord->>Service: Sends interaction request
    Service->>Service: Validate input & feature flag
    Service->>Service: Prepare metadata & DataPacket
    Service->>Handler: Enqueue DataPacket for processing
    Handler->>Handler: Extract parameters from metadata
    Handler->>Utils: Fetch users with role
    Utils-->>Handler: Return members or error
    Handler->>Discord: Send mention(s) or error message
Loading

Assessment against linked issues

Objective (Issue #) Addressed Explanation
Migrate mention each command (#70)

Suggested reviewers

  • pankajjs
  • iamitprakash
  • Achintya-Chatterjee

Poem

A bunny hops with code so neat,
Now every role, you can @-repeat!
With mention-each, the bot’s on track,
Pings for all—no one will lack.
Tests and mocks, all in a row,
This rabbit’s proud to see it go!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🔭 Outside diff range comments (1)
commands/main/resgister_test.go (1)

98-104: ⚠️ Potential issue

Remove duplicate test case.

This test case is identical to the one at lines 91-97. Remove the duplicate to maintain clean test code.

-	t.Run("should panic when openSession.ApplicationCommandCreate() returns an error", func(t *testing.T) {
-		mockSess := &mockSession{openError: nil, commandError: assert.AnError}
-
-		assert.Panics(t, func() {
-			RegisterCommands(mockSess)
-		}, "RegisterCommands should panic when ApplicationCommandCreate returns an error")
-	})
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecf9b55 and 12dacd3.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • commands/handlers/main.go (1 hunks)
  • commands/handlers/mentionEachHandler.go (1 hunks)
  • commands/main.go (1 hunks)
  • commands/main/register.go (1 hunks)
  • commands/main/resgister_test.go (2 hunks)
  • commands/register/register.go (1 hunks)
  • commands/register/register_test.go (2 hunks)
  • config/config.go (1 hunks)
  • dtos/commands.go (1 hunks)
  • go.mod (1 hunks)
  • models/discord.go (1 hunks)
  • models/discord_test.go (2 hunks)
  • service/main.go (1 hunks)
  • service/mentionEachService.go (1 hunks)
  • tests/mocks/discordSessionMock.go (1 hunks)
  • utils/constants.go (1 hunks)
  • utils/membersUtils.go (1 hunks)
  • utils/membersUtils_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
commands/register/register_test.go (1)
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: utils/members_utils_test.go:28-37
Timestamp: 2025-04-11T16:51:24.677Z
Learning: The `ChannelMessageSend` mock implementation in `utils/members_utils_test.go` is necessary because it's part of the `DiscordSessionInterface` interface in the Discord service. In Go, all methods of an interface must be implemented for a mock to properly satisfy the interface, even if some methods aren't directly used in the current tests.
commands/main/resgister_test.go (1)
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: utils/members_utils_test.go:28-37
Timestamp: 2025-04-11T16:51:24.677Z
Learning: The `ChannelMessageSend` mock implementation in `utils/members_utils_test.go` is necessary because it's part of the `DiscordSessionInterface` interface in the Discord service. In Go, all methods of an interface must be implemented for a mock to properly satisfy the interface, even if some methods aren't directly used in the current tests.
tests/mocks/discordSessionMock.go (1)
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: utils/members_utils_test.go:28-37
Timestamp: 2025-04-11T16:51:24.677Z
Learning: The `ChannelMessageSend` mock implementation in `utils/members_utils_test.go` is necessary because it's part of the `DiscordSessionInterface` interface in the Discord service. In Go, all methods of an interface must be implemented for a mock to properly satisfy the interface, even if some methods aren't directly used in the current tests.
🧬 Code Graph Analysis (7)
commands/handlers/main.go (2)
utils/constants.go (1)
  • CommandNames (11-16)
service/main.go (1)
  • CS (14-14)
service/main.go (2)
utils/constants.go (1)
  • CommandNames (11-16)
commands/handlers/main.go (1)
  • CS (18-18)
utils/constants.go (1)
dtos/commands.go (1)
  • CommandNameTypes (3-8)
commands/handlers/mentionEachHandler.go (3)
models/discord.go (2)
  • SessionInterface (33-40)
  • SessionWrapper (5-7)
utils/membersUtils.go (4)
  • GetUsersWithRole (12-55)
  • FormatUserListResponse (79-92)
  • FormatMentionResponse (69-77)
  • FormatUserMentions (57-67)
commands/handlers/main.go (2)
  • CommandHandler (14-16)
  • CreateSession (44-57)
utils/membersUtils.go (2)
models/discord.go (1)
  • SessionInterface (33-40)
utils/constants.go (1)
  • DISCORD_GUILD_MEMBER_API_LIMIT (8-8)
utils/membersUtils_test.go (3)
tests/mocks/discordSessionMock.go (1)
  • DiscordSession (12-14)
utils/constants.go (1)
  • DISCORD_GUILD_MEMBER_API_LIMIT (8-8)
utils/membersUtils.go (4)
  • GetUsersWithRole (12-55)
  • FormatUserMentions (57-67)
  • FormatMentionResponse (69-77)
  • FormatUserListResponse (79-92)
commands/main.go (1)
utils/constants.go (1)
  • CommandNames (11-16)
🔇 Additional comments (24)
commands/main/register.go (1)

33-33: Good fix for the method name typo.

Correcting GetUerId() to GetUserId() ensures proper user ID retrieval during application command creation, which is essential for Discord API integration. This correction maintains consistency with corresponding method signature fixes in other files.

commands/register/register.go (1)

35-35: Typo fix: method name corrected from GetUerId to GetUserId

The method name has been correctly updated to match the standardized interface method name across the codebase. This ensures consistency and proper function calls.

service/main.go (1)

25-26: Properly integrated MentionEach command into the service router

The MentionEach command case has been added to the switch statement following the existing pattern in the codebase. This properly integrates the new command functionality into the service layer.

commands/handlers/main.go (1)

31-32: Properly integrated MentionEach command into the handler router

The MentionEach command case has been correctly added to the MainHandler function, following the established pattern for routing commands to their specific handlers. This implementation properly integrates with the asynchronous message processing system.

models/discord_test.go (3)

10-11: Good practice: replaced external dependency with a local constant

Using a local constant rather than an external dependency reduces coupling and makes the test more maintainable.


13-13: Updated command name reference to use local constant

This change correctly utilizes the newly defined local constant.


44-54: Added test coverage for new session interface methods

Test cases for the newly added GuildMembers and ChannelMessageSend methods have been properly implemented, ensuring the interface remains fully tested. The implementation follows the same pattern as the existing tests, asserting that unimplemented methods panic when called on the dummy session wrapper.

utils/constants.go (2)

5-9: Appropriate constant grouping and new API limit constant added.

The constant grouping provides good organization, and the new DISCORD_GUILD_MEMBER_API_LIMIT constant with value 1000 properly captures Discord's API pagination limit for guild member requests.


15-15: New command registration looks good.

The "mention-each" command is correctly added to the CommandNames struct, consistent with the PR objective of implementing the /mention-each command handler.

commands/register/register_test.go (3)

40-43: Test mock fields properly track the new methods.

These fields appropriately track calls to the newly added methods and allow simulating errors for comprehensive test coverage.


62-65: Method name correctly fixed.

Fixed the typo in method name from GetUerId to GetUserId, maintaining consistency with the interface update in models/discord.go.


67-74: New mock methods properly implemented.

The mock implementations for GuildMembers and ChannelMessageSend correctly set tracking flags and return configured errors, providing the necessary support for testing the new mention-each command functionality.

commands/main/resgister_test.go (3)

40-43: Test mock fields properly track the new methods.

These fields appropriately track calls to the newly added methods and allow simulating errors for comprehensive test coverage.


62-65: Method name correctly fixed.

Fixed the typo in method name from GetUerId to GetUserId, maintaining consistency with the interface update in models/discord.go.


67-75: New mock methods properly implemented.

The mock implementations for GuildMembers and ChannelMessageSend correctly set tracking flags and return configured errors, supporting the testing of the new mention-each command functionality.

models/discord.go (3)

21-23: Method name corrected for consistency.

Fixed the typo in method name from GetUerId to GetUserId for consistency across the codebase.


25-31: New session wrapper methods properly implemented.

The methods GuildMembers and ChannelMessageSend properly delegate to the underlying discordgo Session, providing the functionality needed for the mention-each command to fetch guild members and send messages.


37-39: Session interface appropriately extended.

The interface now includes the corrected GetUserId method and the newly added GuildMembers and ChannelMessageSend methods, ensuring all implementations will provide these required functions.

commands/handlers/mentionEachHandler.go (1)

120-178: Consider guarding against Discord’s 2000-character limit

Batching at 5 users is safe, but individual messages (params.Message + mention) or the final summary may still exceed 2000 characters if params.Message is lengthy. A quick length check before ChannelMessageSend would prevent runtime Discord errors.

Would you like a helper that chunks overly long messages automatically?

utils/membersUtils.go (1)

57-66: 🧹 Nitpick (assertive)

Tiny nit: pre-allocate string slice with exact capacity

You already know the exact length (len(members)), so pre-allocating is great.
For readability you can inline the literal:

-mentions := make([]string, 0, len(members))
+mentions := make([]string, len(members))
 for i, member := range members {
-    if member.User != nil {
-        mentions = append(mentions, fmt.Sprintf("<@%s>", member.User.ID))
+    if member.User != nil {
+        mentions[i] = fmt.Sprintf("<@%s>", member.User.ID)
     } else {
         ...

This avoids the bounds checks on each append.
Not critical but an easy micro-optimisation.

Likely an incorrect or invalid review comment.

commands/main.go (1)

47-60: 🧹 Nitpick (assertive)

Slash-command option names: avoid underscores for better UX

Discord’s CLI autocompleter treats dashes (-) more naturally than underscores (_).
Consider renaming dev_titledev-title (and the corresponding handler lookup) for consistency with common slash-command conventions.

No code change needed for functionality, but improves discoverability.

⛔ Skipped due to learnings
Learnt from: Devashish08
PR: Real-Dev-Squad/discord-service#80
File: commands/main.go:56-57
Timestamp: 2025-04-10T14:08:41.301Z
Learning: In the discord-service codebase, command option names consistently use lowercase for single words and snake_case for multi-word names (e.g., `dev_title`), not camelCase.
config/config.go (1)

7-7: Approve import reorder.

The import of "os" has been moved within the block, but this only affects formatting and has no impact on functionality.

go.mod (1)

19-19: Verify necessity of new indirect dependency.

An indirect dependency on github.com/stretchr/objx v0.5.2 was added. Please confirm that this package is actually referenced in the codebase (e.g., in tests or mock utilities), and if not, remove it by running go mod tidy.

dtos/commands.go (1)

7-7: Ensure MentionEach is initialized.

The new MentionEach field in CommandNameTypes defaults to an empty string. Verify that it’s assigned the "mention-each" command name in your configuration or initializer; otherwise, downstream handlers relying on this value may fail.

t.Run("SessionWrapper should always implement getUerId() method", func(t *testing.T) {
assert.Panics(t, func() {
sessionWrapper.GetUerId()
sessionWrapper.GetUserId()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Method name corrected from getUerId to GetUserId

The method name has been properly updated to match the standardized interface method name used throughout the codebase.

Consider also updating the error message on line 42 from "getUerId" to "GetUserId" for complete consistency:

-		}, "should panic when getUerId() is called")
+		}, "should panic when GetUserId() is called")

Comment on lines +12 to +14
type DiscordSession struct {
mock.Mock
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Add compile-time interface assertion to catch drift early

If the SessionInterface ever changes, we’ll discover it at compile time instead of at runtime when the tests start failing with an opaque mock error.

 type DiscordSession struct {
 	mock.Mock
 }
+
+// Assert implementation at compile-time
+var _ models.SessionInterface = (*DiscordSession)(nil)
-import (
-	"fmt"
-	"github.com/bwmarrin/discordgo"
-	"github.com/stretchr/testify/mock"
-)
+import (
+	"fmt"
+	"github.com/Real-Dev-Squad/discord-service/models"
+	"github.com/bwmarrin/discordgo"
+	"github.com/stretchr/testify/mock"
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type DiscordSession struct {
mock.Mock
}
import (
"fmt"
"github.com/Real-Dev-Squad/discord-service/models"
"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/mock"
)
type DiscordSession struct {
mock.Mock
}
// Assert implementation at compile-time
var _ models.SessionInterface = (*DiscordSession)(nil)

Comment on lines +3 to +10
import (
// Import the actual interface definition

"fmt"

"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/mock"
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Prune placeholder comment & let goimports manage spacing

The “Import the actual interface definition” comment and the blank lines inside the import block are stale and break the canonical goimports layout. Keeping the imports tightly grouped prevents accidental merge conflicts and lets the formatter do its job.

-import (
-	// Import the actual interface definition
-
-	"fmt"
-
-	"github.com/bwmarrin/discordgo"
-	"github.com/stretchr/testify/mock"
-)
+import (
+	"fmt"
+
+	"github.com/bwmarrin/discordgo"
+	"github.com/stretchr/testify/mock"
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import (
// Import the actual interface definition
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/mock"
)
import (
"fmt"
"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/mock"
)

Comment on lines +1 to +12
package utils

import (
"errors"
"fmt"
"strings"
"testing"

"github.com/Real-Dev-Squad/discord-service/tests/mocks"
"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/assert"
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Run tests in parallel for faster feedback

None of the sub-tests share state, and each creates its own mock. Marking the top-level test functions with t.Parallel() speeds up CI without additional risk.

-func TestGetUsersWithRole(t *testing.T) {
+func TestGetUsersWithRole(t *testing.T) {
+	t.Parallel()

Repeat the pattern for the other top-level test functions (TestFormatUserMentions, TestFormatMentionResponse, TestFormatUserListResponse).

Comment on lines +55 to +61
userMessage := metaData["message"]
if len(userMessage) > MaxUserMessageLength {
truncatedSuffix := "..."
logrus.Warnf("User provided message length (%d) exceeds limit (%d). Truncating.", len(userMessage), MaxUserMessageLength)
userMessage = userMessage[:MaxUserMessageLength] + truncatedSuffix
}
params.Message = userMessage
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Truncation can overshoot the advertised limit

You slice to MaxUserMessageLength and then append "...", yielding a final length of MaxUserMessageLength + 3. This defeats the purpose of the guard.

-		if len(userMessage) > MaxUserMessageLength {
-			truncatedSuffix := "..."
-			logrus.Warnf("User provided message length (%d) exceeds limit (%d). Truncating.", len(userMessage), MaxUserMessageLength)
-			userMessage = userMessage[:MaxUserMessageLength] + truncatedSuffix
+		if len(userMessage) > MaxUserMessageLength {
+			const truncatedSuffix = "..."
+			cutoff := MaxUserMessageLength - len(truncatedSuffix)
+			if cutoff < 0 {
+				cutoff = 0
+			}
+			logrus.Warnf("User provided message length (%d) exceeds limit (%d). Truncating.", len(userMessage), MaxUserMessageLength)
+			userMessage = userMessage[:cutoff] + truncatedSuffix
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
userMessage := metaData["message"]
if len(userMessage) > MaxUserMessageLength {
truncatedSuffix := "..."
logrus.Warnf("User provided message length (%d) exceeds limit (%d). Truncating.", len(userMessage), MaxUserMessageLength)
userMessage = userMessage[:MaxUserMessageLength] + truncatedSuffix
}
params.Message = userMessage
userMessage := metaData["message"]
if len(userMessage) > MaxUserMessageLength {
const truncatedSuffix = "..."
cutoff := MaxUserMessageLength - len(truncatedSuffix)
if cutoff < 0 {
cutoff = 0
}
logrus.Warnf("User provided message length (%d) exceeds limit (%d). Truncating.", len(userMessage), MaxUserMessageLength)
userMessage = userMessage[:cutoff] + truncatedSuffix
}
params.Message = userMessage

Comment on lines +12 to +15
func GetUsersWithRole(session models.SessionInterface, guildID string, roleID string) ([]discordgo.Member, error) {
var membersWithRole []discordgo.Member
lastMemberID := ""

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Prefer pointer slices to avoid unnecessary memory copies

membersWithRole is declared as []discordgo.Member, causing every matched member object to be copied into the slice.
Because discordgo.Member contains nested structs and pointers, the copy can be relatively expensive for large guilds.

-var membersWithRole []discordgo.Member
+var membersWithRole []*discordgo.Member

You would then append member directly (without the * dereference) and adjust the return type.
Down-stream helpers (FormatUserMentions, FormatUserListResponse) would need a tiny signature tweak but will benefit from reduced allocations.

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +32 to +50
foundInChunk := 0
lastIdInChunk := ""
for _, member := range membersChunk {
if member.User == nil {
logrus.Warnf("Guild %s: Member object or User data is nil, skipping member: %+v", guildID, member)
continue
}
for _, r := range member.Roles {
if r == roleID {
membersWithRole = append(membersWithRole, *member)
foundInChunk++
break
}
}

lastIdInChunk = member.User.ID
}
lastMemberID = lastIdInChunk
logrus.Debugf("Found %d members with role %s in this chunk (Guild %s)", foundInChunk, roleID, guildID)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential infinite-loop when entire chunk has nil users

lastIdInChunk is updated only inside the loop and after the nil-user check.
If a chunk contains only entries with member.User == nil, lastIdInChunk remains the previous value, lastMemberID is unchanged, and the next GuildMembers call retrieves the very same page → infinite loop.

-        for _, member := range membersChunk {
+        for _, member := range membersChunk {
             if member.User == nil {
                 ...
                 continue
             }
             ...
             lastIdInChunk = member.User.ID
         }
-        lastMemberID = lastIdInChunk
+        if lastIdInChunk == "" {
+            // no progress possible – bail out to avoid infinite loop
+            break
+        }
+        lastMemberID = lastIdInChunk

Consider early-breaking as shown or always advancing lastMemberID with the last raw member to guarantee progress.

Comment on lines +52 to +58
roleID, ok := roleOption.Value.(string)
if !ok {
errorMsg := "Invalid role format(not a String)"
logrus.Errorf("%s: Expected string, got %T", errorMsg, roleOption.Value)
sendErrorResponse(response, errorMsg)
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: roleID is shadowed, producing an empty value downstream

Using := redeclares roleID inside this block, leaving the outer roleID (used later for logs, metadata, and response content) unchanged and still empty.
Consequently the function will hit the “empty ID” check and return an error.

- roleID, ok := roleOption.Value.(string)
+ var ok bool
+ roleID, ok = roleOption.Value.(string)

Or, clearer:

roleIDStr, ok := roleOption.Value.(string)
if !ok { ... }
roleID = roleIDStr

Make sure accompanying unit tests cover this path.

Comment on lines +82 to +92
logrus.Infof("Mention-each options: role=%s, message=%s, dev=%v, devTitle=%v", roleID, message, dev, devTitle)

var responseContent string
if devTitle {
responseContent = fmt.Sprintf("Fetching users with the <@&%s>", roleID)
} else if dev {
responseContent = fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
} else {
responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider re-phrasing user feedback for clarity

Responses like
"Mentioning all users with the <@&123456>"
lack the word “role” and may confuse end-users. A tiny wording tweak adds clarity:

- responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
+ responseContent = fmt.Sprintf("Mentioning all users with the <@&%s> role", roleID)

Same for the other branches.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
logrus.Infof("Mention-each options: role=%s, message=%s, dev=%v, devTitle=%v", roleID, message, dev, devTitle)
var responseContent string
if devTitle {
responseContent = fmt.Sprintf("Fetching users with the <@&%s>", roleID)
} else if dev {
responseContent = fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
} else {
responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
}
logrus.Infof("Mention-each options: role=%s, message=%s, dev=%v, devTitle=%v", roleID, message, dev, devTitle)
var responseContent string
if devTitle {
responseContent = fmt.Sprintf("Fetching users with the <@&%s>", roleID)
} else if dev {
responseContent = fmt.Sprintf("Sending individual mentions to users with the <@&%s>", roleID)
} else {
- responseContent = fmt.Sprintf("Mentioning all users with the <@&%s>", roleID)
+ responseContent = fmt.Sprintf("Mentioning all users with the <@&%s> role", roleID)
}

Comment on lines +93 to +100
metaData := map[string]string{
"role_id": roleID,
"message": message,
"dev": fmt.Sprintf("%v", dev),
"dev_title": fmt.Sprintf("%v", devTitle),
"guild_id": s.discordMessage.Data.GuildId,
"channel_id": s.discordMessage.ChannelId,
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Store booleans as booleans in metadata

metaData converts every value to string.
If downstream consumers ever need to read dev / dev_title as booleans they will have to parse the string again.

- "dev":        fmt.Sprintf("%v", dev),
- "dev_title":  fmt.Sprintf("%v", devTitle),
+ "dev":        strconv.FormatBool(dev),
+ "dev_title":  strconv.FormatBool(devTitle),

(Standard strconv is already in fmt’s transitive deps.)

Not a blocker, but preserves type information.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
metaData := map[string]string{
"role_id": roleID,
"message": message,
"dev": fmt.Sprintf("%v", dev),
"dev_title": fmt.Sprintf("%v", devTitle),
"guild_id": s.discordMessage.Data.GuildId,
"channel_id": s.discordMessage.ChannelId,
}
metaData := map[string]string{
"role_id": roleID,
"message": message,
"dev": strconv.FormatBool(dev),
"dev_title": strconv.FormatBool(devTitle),
"guild_id": s.discordMessage.Data.GuildId,
"channel_id": s.discordMessage.ChannelId,
}

Comment on lines +21 to +28
type CommandParams struct {
RoleID string
ChannelID string
GuildID string
Message string
Dev bool
DevTitle bool
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is particular to this command only?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes commandParams defined here hold the specific parameters needed only for the mentionEach command

Comment on lines +15 to +20
const (
BatchSize = 5
BatchDelay = 1 * time.Second
MaxUserMessageLength = 1000
)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be reused for other commands?

Copy link
Contributor Author

@Devashish08 Devashish08 Apr 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont know that for now have to see usecases for other command for that

)

func (s *CommandHandler) mentionEachHandler() error {
logrus.Info("Processing mention-each command")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this required?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not required but put there for easy debugging and tracing

}

var (
extractCommandParamsFunc = func(metaData map[string]string) (CommandParams, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put better name for this function.
Also this is something which is common across other commands. we should create this as util function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate mention each command

2 participants