Skip to content

Commit

Permalink
Dev to Main Sync (#42)
Browse files Browse the repository at this point in the history
* Implemented Queue (#36)

* Chore : Updated .env.example

* Feat : Added rabbitmq

* Feat : Added exponential retry method

* Feat: Added DTO for message

* Bug : Fixed ExponentialBackoffRetry function

* Feat : Added ExponentialBackoffRetry helper

* Feat : Added Queue code

* Test : Updated Test

* Feat : Updated config to load queue name

* Feat : Added QUEUE_URL in env

* Doc : Updated readme

* Chore : Updated order

* Chore : Fixed broken test and added error handling

* Feat : Added queue handler (#37)

* Chore : Updated .env.example

* Feat : Added rabbitmq

* Feat : Added exponential retry method

* Feat: Added DTO for message

* Bug : Fixed ExponentialBackoffRetry function

* Feat : Added ExponentialBackoffRetry helper

* Feat : Added Queue code

* Test : Updated Test

* Feat : Updated config to load queue name

* Feat : Added QUEUE_URL in env

* Doc : Updated readme

* Chore : Updated order

* Chore : Fixed broken test and added error handling

* Feat : Added queue handler

* Add QUEUE_URL and QUEUE_NAME env  (#39)

* Chore : Updated .env.example

* Feat : Added rabbitmq

* Feat : Added exponential retry method

* Feat: Added DTO for message

* Bug : Fixed ExponentialBackoffRetry function

* Feat : Added ExponentialBackoffRetry helper

* Feat : Added Queue code

* Test : Updated Test

* Feat : Updated config to load queue name

* Feat : Added QUEUE_URL in env

* Doc : Updated readme

* Chore : Updated order

* Chore : Fixed broken test and added error handling

* Feat : Added queue handler

* chore: add queuename and url env

---------

Co-authored-by: Joy <[email protected]>

* replace env to vars (#41)

* Feat/listening command (#43)

* Feat : Added listening command

* Feat : Added helper to DataPacket

* Feat : Updated SendMessage Code

* Test : Fixed test for DataPacket

* Feat : Added listening service

* Chore : Updated function declaration

* Chore : Updated InteractionResponseData

* Feat : Addded end-to-end implementation of listening command

* Feat : Build common package for discord

* WIP : Creating methods for discord

* Test : Added test for commandHandler.MainHandler in QueueHandler

* Test : Added test for QueueHandler

* Test : Added test for MainHandler

* Test : Added test

* Test : Added test

* Chore : Removed consoles

* Chore : Removed fmt

* Chore : Minor change

* Chore : Minor change

* feat : Moved register command flow within main.go (#45)

* Feat : Moved register command flow within main.go

* Refactor : Updated vars & functions name

* Update commands/register/register.go

Co-authored-by: Yash Raj <[email protected]>

* Chore : Minor change

* Chore : Minor change

---------

Co-authored-by: Amit Prakash <[email protected]>
Co-authored-by: Yash Raj <[email protected]>

---------

Co-authored-by: Prakash Choudhary <[email protected]>
Co-authored-by: Amit Prakash <[email protected]>
Co-authored-by: Yash Raj <[email protected]>
  • Loading branch information
4 people authored Feb 1, 2025
1 parent 5b190d7 commit 59d2e4e
Show file tree
Hide file tree
Showing 34 changed files with 1,160 additions and 105 deletions.
8 changes: 6 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
PORT = 8999
DISCORD_PUBLIC_KEY = "<DISCORD_PUBLIC_KEY>"
PORT = "<PORT>" # Default :8999
DISCORD_PUBLIC_KEY = "<DISCORD_PUBLIC_KEY>"
GUILD_ID = "<DISCORD_GUILD_ID>"
BOT_TOKEN = "<BOT_TOKEN>"
DISCORD_QUEUE = "<DISCORD_QUEUE>" # Default :DISCORD_QUEUE
QUEUE_URL = "<QUEUE_URL>" # Default :amqp://localhost
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,6 @@ jobs:
-e DISCORD_PUBLIC_KEY=${{secrets.DISCORD_PUBLIC_KEY}} \
-e BOT_TOKEN=${{secrets.BOT_TOKEN}} \
-e GUILD_ID=${{secrets.GUILD_ID}} \
-e QUEUE_NAME=${{vars.QUEUE_NAME}} \
-e QUEUE_URL=${{vars.QUEUE_URL}} \
${{ secrets.DOCKERHUB_USERNAME }}/${{ github.event.repository.name }}
5 changes: 0 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ clean:
@rm -rf coverage.out
@rm -rf coverage.html

register:
@echo "Registering commands..."
@go run commands/main/register.go
@echo "Registration complete."

test-cover:
ifeq ($(FORCE),1)
@echo "Force flag detected. Cleaning before tests..."
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,31 @@ Before running the project, ensure that you have the following installed:
To install Air, follow the installation steps here:
[Air Installation Guide](https://github.com/air-verse/air)

## Running RabbitMQ with Docker

1. Ensure Docker is installed and running on your machine.
2. Navigate to the project directory.
3. Create a `docker-compose.yml` file with the following content:

```yaml
version: '3.8'

services:
rabbitmq:
image: rabbitmq:3.13-management
container_name: rabbitmq
ports:
- '5672:5672'
- '15672:15672'
```
4. Start the RabbitMQ container:
```sh
docker-compose up -d
```

5. Verify that RabbitMQ is running by accessing the management interface at [http://localhost:15672](http://localhost:15672). The default username and password are both `guest`.

## Running the Project Using Go

Expand Down Expand Up @@ -62,7 +87,6 @@ Before running the project, ensure that you have the following installed:
air
```


## Running the Project Using Make

You can run the project using the `Makefile`, which provides several commands for various tasks. Below are the steps to run the project:
Expand Down
18 changes: 1 addition & 17 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,34 @@
To setup a discord you first need to create an application (basically a bot), here are the steps to follow:

1. Visit [Discord Application](https://discord.com/developers/applications 'Discord Application')

2. Cick on "New Application" Button
![Screenshot 2024-11-08 at 1 52 53 AM](https://github.com/user-attachments/assets/380657ca-89b4-4053-96c9-6b73632d382c)

3. Fill in your Application Name
![Screenshot 2024-11-08 at 1 52 53 AM](https://github.com/user-attachments/assets/688bd69d-fcca-4a80-8780-9ab18bfc5037)
4. Now [here](https://discord.com/developers/applications) you will see your newly created application, click on it, this should open _"General Information"_

5. If you scroll down, you will se _PUBLIC KEY_, copy it and place it `.env` as _DISCORD_PUBLIC_KEY_

6. Now to create BOT_TOKEN, click on BOT > Reset Token
![Screenshot 2024-11-08 at 2 07 40 AM](https://github.com/user-attachments/assets/201f9e51-a44a-43af-9c96-4eaf453d02b0)

7. Once you have the token, place it against _BOT_TOKEN_ in `.env`

8. Now will be creating an invite URL and for that you need to click on OAuth2 > bot
![Screenshot 2024-11-13 at 11 40 21 PM](https://github.com/user-attachments/assets/aebad7fe-aa82-45de-bb17-25dc0fff0e5f)

9. Now as soon as you click on bot, a section to choose bot permission from, will shown up
![Screenshot 2024-11-14 at 10 53 20 AM](https://github.com/user-attachments/assets/b6fc4afb-4de4-449c-bf39-f8a0b4d3de06)

10. Check the following options

1. [ ] Send Messages

11. Once you select all the bot permissions, scroll a bit down and you will see "Generated URL"
![Screenshot 2024-11-14 at 10 58 30 AM](https://github.com/user-attachments/assets/bbff4c6d-4ef5-46fd-89c7-9acf31c11cdd)

12. Copy and paste that URL in browser, a prompt will come up where it will ask you to select you own "Discord Server"
![Screenshot 2024-11-14 at 11 00 45 AM](https://github.com/user-attachments/assets/322caf6d-af84-4752-88db-0ce64e080d6d)

13. Once you add the Bot into your server, copy the "Server Id", by right clicking on the server avatar. Now place this id in `.env` against _GUILD_ID_

# Connecting Discord Service with Discord

Now as you have created the discord bot, now its time to connect it with discord service using the following steps:

1. Even before setting up the connection, you would need to register the commands first. That can be done using the following

```bash
make register #or go run commands/main/register.go
```

1. You would need to register the commands first. That will be auto handled once you start the server
2. Now start the server using

```bash
Expand All @@ -62,5 +47,4 @@ Since we are considering 8999 as default port for this service. If you wish to c

4. Copy the Ngrok URL and open the General Information on [Discord Developer Portal](https://discord.com/developers/applications) of your bot, paste the copied URL in Interactions Endpoint URL
![Screenshot 2024-11-14 at 10 58 30 AM](https://github.com/user-attachments/assets/53f372e4-44e7-4cdc-acfc-0e3b707f8607)

5. All Set 🚀🚀🚀. Now you can start with running hello command
19 changes: 19 additions & 0 deletions commands/handlers/listeningHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package handlers

import (
"fmt"
"strings"

"github.com/Real-Dev-Squad/discord-service/utils"
)

func (s *CommandHandler) listeningHandler() error {
metaData := s.discordMessage.MetaData
nickName := metaData["nickname"]
if metaData["value"] == "true" {
nickName = fmt.Sprintf("%s%s%s", utils.NICKNAME_PREFIX, nickName, utils.NICKNAME_SUFFIX)
} else {
nickName = strings.TrimPrefix(strings.TrimSuffix(nickName, utils.NICKNAME_SUFFIX), utils.NICKNAME_PREFIX)
}
return UpdateNickName(s.discordMessage.UserID, nickName)
}
63 changes: 63 additions & 0 deletions commands/handlers/listeningHandler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package handlers

import (
"testing"

"github.com/Real-Dev-Squad/discord-service/dtos"
_ "github.com/Real-Dev-Squad/discord-service/tests/helpers"
"github.com/Real-Dev-Squad/discord-service/utils"
"github.com/stretchr/testify/assert"
)

type MockCommandHandler struct {
discordMessage *dtos.DataPacket
}

func TestListeningHandler(t *testing.T) {

t.Run("should update nickname with prefix and suffix if value is true", func(t *testing.T) {

dataPacket := &dtos.DataPacket{
UserID: "userID",
MetaData: map[string]string{
"nickname": "testNick",
"value": "true",
},
}

handler := &CommandHandler{discordMessage: dataPacket}
err := handler.listeningHandler()
assert.Error(t, err)
})

t.Run("should update nickname without prefix and suffix if value is false", func(t *testing.T) {

dataPacket := &dtos.DataPacket{
UserID: "userID",
MetaData: map[string]string{
"nickname": utils.NICKNAME_PREFIX + "testNick" + utils.NICKNAME_SUFFIX,
"value": "false",
},
}

handler := &CommandHandler{discordMessage: dataPacket}
err := handler.listeningHandler()
assert.Error(t, err)
})

t.Run("should return error if UpdateNickName fails", func(t *testing.T) {

dataPacket := &dtos.DataPacket{
UserID: "userID",
MetaData: map[string]string{
"nickname": "testNick",
"value": "true",
},
}

handler := &CommandHandler{discordMessage: dataPacket}
err := handler.listeningHandler()
assert.Error(t, err)
assert.Equal(t, "websocket: close 4004: Authentication failed.", err.Error())
})
}
71 changes: 71 additions & 0 deletions commands/handlers/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package handlers

import (
"errors"

"github.com/Real-Dev-Squad/discord-service/config"
"github.com/Real-Dev-Squad/discord-service/dtos"
"github.com/Real-Dev-Squad/discord-service/models"
"github.com/bwmarrin/discordgo"
"github.com/sirupsen/logrus"
)

type CommandHandler struct {
discordMessage *dtos.DataPacket
}

var CS = CommandHandler{}

func MainHandler(dataPacket []byte) func() error {
packetData := &dtos.DataPacket{}
err := packetData.FromByte(dataPacket)
if err != nil {
logrus.Errorf("Failed to unmarshal data send by queue: %v", err)
return nil
}
CS.discordMessage = packetData
switch packetData.CommandName {
case "listening":
return CS.listeningHandler
default:
logrus.Warn("Invalid Command Received: ", packetData.CommandName)
return nil
}
}

type DiscordSession struct {
session *discordgo.Session
}

var NewDiscord = discordgo.New
var CreateSession = func() (*discordgo.Session, error) {
session, err := NewDiscord("Bot " + config.AppConfig.BOT_TOKEN)
if err != nil {
logrus.Errorf("Cannot create a new Discord session: %v", err)
return nil, err
}
openSession := &models.SessionWrapper{Session: session}
err = openSession.Open()
if err != nil {
logrus.Errorf("Cannot open the session: %v", err)
return nil, err
}
return session, nil
}

func UpdateNickName(userId string, newNickName string) error {
if len(newNickName) > 32 {
logrus.Error("Must be 32 or fewer in length.")
return errors.New("Must be 32 or fewer in length.")
}
session, err := CreateSession()
if err != nil {
return err
}
err = session.GuildMemberNickname(config.AppConfig.GUILD_ID, userId, newNickName)
if err != nil {
logrus.Errorf("Cannot update nickname: %v", err)
return nil
}
return nil
}
79 changes: 79 additions & 0 deletions commands/handlers/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package handlers

import (
"errors"
"testing"

"github.com/Real-Dev-Squad/discord-service/dtos"
_ "github.com/Real-Dev-Squad/discord-service/tests/helpers"
"github.com/bwmarrin/discordgo"
"github.com/stretchr/testify/assert"
)

func TestMainHandler(t *testing.T) {
t.Run("should return listeningHandler for 'listening' command", func(t *testing.T) {
dataPacket := &dtos.DataPacket{
CommandName: "listening",
}
data, err := dataPacket.ToByte()
assert.NoError(t, err)

handler := MainHandler(data)
assert.NotNil(t, handler)
})
t.Run("should return nil for invalid data", func(t *testing.T) {
invalidData := []byte(`{"invalid": "data"}`)
handler := MainHandler(invalidData)
assert.Nil(t, handler)
})
}

func TestCreateSession(t *testing.T) {
t.Run("should fail if NewDiscord returns an error", func(t *testing.T) {
originalNewDiscord := NewDiscord
defer func() { NewDiscord = originalNewDiscord }()
NewDiscord = func(token string) (s *discordgo.Session, err error) {
return nil, errors.New("testing error")
}
_, err := CreateSession()
assert.Error(t, err)
})
t.Run("should initiate open session if NewDiscord returns no error", func(t *testing.T) {
originalNewDiscord := NewDiscord
defer func() { NewDiscord = originalNewDiscord }()
NewDiscord = func(token string) (s *discordgo.Session, err error) {
return &discordgo.Session{}, nil
}
assert.Panics(t, func() { CreateSession() })
})
}

func mockCreateSession() (*discordgo.Session, error) {
return &discordgo.Session{}, nil
}
func TestUpdateNickName(t *testing.T) {

var originalCreateSession = CreateSession
defer func() { CreateSession = originalCreateSession }()

t.Run("should return error if newNickName is longer than 32 characters", func(t *testing.T) {
err := UpdateNickName("userID", "ThisIsAVeryLongNicknameThatExceedsTheLimit")
assert.Error(t, err)
assert.Equal(t, "Must be 32 or fewer in length.", err.Error())
})

t.Run("should return error if CreateSession fails", func(t *testing.T) {
CreateSession = func() (*discordgo.Session, error) {
return nil, errors.New("failed to create session")
}
err := UpdateNickName("userID", "validNickname")
assert.Error(t, err)
assert.Equal(t, "failed to create session", err.Error())
})
t.Run("should hit GuildMemberNickname if CreateSession succeeds", func(t *testing.T) {
CreateSession = mockCreateSession
assert.Panics(t, func() { UpdateNickName("userID", "validNickname") })

})

}
12 changes: 12 additions & 0 deletions commands/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,16 @@ var Commands = []*discordgo.ApplicationCommand{
Name: "hello",
Description: "Greets back with hello!",
},
{
Name: "listening",
Description: "mark user as listening",
Options: []*discordgo.ApplicationCommandOption{
{
Name: "value",
Description: "to enable or disable the listening mode",
Type: 5,
Required: true,
},
},
},
}
Loading

0 comments on commit 59d2e4e

Please sign in to comment.