Skip to content

Commit 10d703f

Browse files
committed
Add discord integration
1 parent d1367cc commit 10d703f

File tree

11 files changed

+443
-177
lines changed

11 files changed

+443
-177
lines changed

api/cmd/fcm/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"context"
66
"encoding/json"
77
"fmt"
8-
"io/ioutil"
8+
"io"
99
"log"
1010
"math/rand"
1111
"net/http"
@@ -83,7 +83,7 @@ func createSmsMessage() error {
8383
return stacktrace.Propagate(err, "cannot do http request")
8484
}
8585

86-
body, err := ioutil.ReadAll(response.Body)
86+
body, err := io.ReadAll(response.Body)
8787
if err != nil {
8888
return stacktrace.Propagate(err, "cannot read response body")
8989
}

api/pkg/di/container.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ func NewContainer(projectID string, version string) (container *Container) {
115115
container.RegisterWebhookListeners()
116116

117117
container.RegisterLemonsqueezyRoutes()
118+
118119
container.RegisterDiscordRoutes()
120+
container.RegisterDiscordListeners()
119121

120122
// this has to be last since it registers the /* route
121123
container.RegisterSwaggerRoutes()
@@ -641,6 +643,7 @@ func (container *Container) DiscordService() (service *services.DiscordService)
641643
container.Tracer(),
642644
container.DiscordClient(),
643645
container.DiscordRepository(),
646+
container.EventDispatcher(),
644647
)
645648
}
646649

@@ -860,6 +863,9 @@ func (container *Container) DiscordHandler() (handler *handlers.DiscordHandler)
860863
container.Tracer(),
861864
container.DiscordHandlerValidator(),
862865
container.DiscordService(),
866+
container.MessageService(),
867+
container.BillingService(),
868+
container.MessageHandlerValidator(),
863869
)
864870
}
865871

@@ -976,6 +982,20 @@ func (container *Container) RegisterBillingListeners() {
976982
}
977983
}
978984

985+
// RegisterDiscordListeners registers event listeners for listeners.DiscordListener
986+
func (container *Container) RegisterDiscordListeners() {
987+
container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.DiscordListener{}))
988+
_, routes := listeners.NewDiscordListener(
989+
container.Logger(),
990+
container.Tracer(),
991+
container.DiscordService(),
992+
)
993+
994+
for event, handler := range routes {
995+
container.EventDispatcher().Subscribe(event, handler)
996+
}
997+
}
998+
979999
// RegisterWebhookListeners registers event listeners for listeners.WebhookListener
9801000
func (container *Container) RegisterWebhookListeners() {
9811001
container.logger.Debug(fmt.Sprintf("registering listeners for %T", listeners.WebhookListener{}))

api/pkg/discord/channel_service.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type ChannelService service
1313
// CreateMessage sends a message to a guild text or DM channel.
1414
//
1515
// API Docs: https://discord.com/developers/docs/resources/channel#create-message
16-
func (service *ChannelService) CreateMessage(ctx context.Context, channelID string, payload map[string]any) (*map[string]any, *Response, error) {
16+
func (service *ChannelService) CreateMessage(ctx context.Context, channelID string, payload map[string]any) (map[string]any, *Response, error) {
1717
request, err := service.client.newRequest(ctx, http.MethodPost, fmt.Sprintf("/channels/%s/messages", channelID), payload)
1818
if err != nil {
1919
return nil, nil, err
@@ -24,8 +24,8 @@ func (service *ChannelService) CreateMessage(ctx context.Context, channelID stri
2424
return nil, response, err
2525
}
2626

27-
message := new(map[string]any)
28-
if err = json.Unmarshal(*response.Body, message); err != nil {
27+
message := map[string]any{}
28+
if err = json.Unmarshal(*response.Body, &message); err != nil {
2929
return nil, response, err
3030
}
3131

@@ -35,7 +35,7 @@ func (service *ChannelService) CreateMessage(ctx context.Context, channelID stri
3535
// Get a channel by ID
3636
//
3737
// API Docs: https://discord.com/developers/docs/resources/channel#get-channel
38-
func (service *ChannelService) Get(ctx context.Context, channelID string) (*map[string]any, *Response, error) {
38+
func (service *ChannelService) Get(ctx context.Context, channelID string) (map[string]any, *Response, error) {
3939
request, err := service.client.newRequest(ctx, http.MethodGet, fmt.Sprintf("/channels/%s", channelID), nil)
4040
if err != nil {
4141
return nil, nil, err
@@ -46,8 +46,8 @@ func (service *ChannelService) Get(ctx context.Context, channelID string) (*map[
4646
return nil, response, err
4747
}
4848

49-
channel := new(map[string]any)
50-
if err = json.Unmarshal(*response.Body, channel); err != nil {
49+
channel := map[string]any{}
50+
if err = json.Unmarshal(*response.Body, &channel); err != nil {
5151
return nil, response, err
5252
}
5353

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package events
2+
3+
import (
4+
"github.com/NdoleStudio/httpsms/pkg/entities"
5+
"github.com/google/uuid"
6+
)
7+
8+
// EventTypeDiscordMessageFailed is emitted when we can't send a discord message
9+
const EventTypeDiscordMessageFailed = "discord.message.failed"
10+
11+
// DiscordMessageFailedPayload is the payload of the EventTypeDiscordMessageFailed event
12+
type DiscordMessageFailedPayload struct {
13+
DiscordID uuid.UUID `json:"discord_id"`
14+
UserID entities.UserID `json:"user_id"`
15+
MessageID uuid.UUID `json:"message_id"`
16+
EventType string `json:"event_type"`
17+
HTTPStatusCode int `json:"http_status_code"`
18+
ErrorMessage string `json:"error_message"`
19+
DiscordChannelID string `json:"discord_channel_id"`
20+
}

api/pkg/handlers/discord_handler.go

Lines changed: 144 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ package handlers
22

33
import (
44
"bytes"
5+
"context"
56
"crypto/ed25519"
67
"encoding/hex"
78
"encoding/json"
89
"fmt"
910
"os"
1011

12+
"github.com/NdoleStudio/httpsms/pkg/entities"
13+
1114
"github.com/google/uuid"
1215

1316
"github.com/NdoleStudio/httpsms/pkg/repositories"
@@ -23,10 +26,13 @@ import (
2326
// DiscordHandler handles discord events
2427
type DiscordHandler struct {
2528
handler
26-
logger telemetry.Logger
27-
tracer telemetry.Tracer
28-
validator *validators.DiscordHandlerValidator
29-
service *services.DiscordService
29+
logger telemetry.Logger
30+
tracer telemetry.Tracer
31+
billingService *services.BillingService
32+
messageValidator *validators.MessageHandlerValidator
33+
validator *validators.DiscordHandlerValidator
34+
service *services.DiscordService
35+
messageService *services.MessageService
3036
}
3137

3238
// NewDiscordHandler creates a new DiscordHandler
@@ -35,12 +41,18 @@ func NewDiscordHandler(
3541
tracer telemetry.Tracer,
3642
validator *validators.DiscordHandlerValidator,
3743
service *services.DiscordService,
44+
messageService *services.MessageService,
45+
billingService *services.BillingService,
46+
messageValidator *validators.MessageHandlerValidator,
3847
) (h *DiscordHandler) {
3948
return &DiscordHandler{
40-
logger: logger.WithService(fmt.Sprintf("%T", h)),
41-
tracer: tracer,
42-
validator: validator,
43-
service: service,
49+
logger: logger.WithService(fmt.Sprintf("%T", h)),
50+
tracer: tracer,
51+
validator: validator,
52+
service: service,
53+
messageService: messageService,
54+
billingService: billingService,
55+
messageValidator: messageValidator,
4456
}
4557
}
4658

@@ -244,7 +256,7 @@ func (h *DiscordHandler) Store(c *fiber.Ctx) error {
244256
// @Failure 500 {object} responses.InternalServerError
245257
// @Router /discord/event [post]
246258
func (h *DiscordHandler) Event(c *fiber.Ctx) error {
247-
_, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
259+
ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
248260
defer span.End()
249261

250262
if verified := h.verifyInteraction(ctxLogger, c); !verified {
@@ -265,48 +277,147 @@ func (h *DiscordHandler) Event(c *fiber.Ctx) error {
265277
}
266278

267279
if payload["type"].(float64) == 2 {
280+
return h.sendSMS(ctx, c, payload)
281+
}
282+
283+
return h.responseBadRequest(c, stacktrace.NewError(fmt.Sprintf("unknown type [%d]", payload["type"])))
284+
}
285+
286+
func (h *DiscordHandler) createRequest(payload map[string]any) requests.MessageSend {
287+
getOption := func(name string) string {
288+
for _, option := range payload["data"].(map[string]any)["options"].([]any) {
289+
if option.(map[string]any)["name"].(string) == name {
290+
return option.(map[string]any)["value"].(string)
291+
}
292+
}
293+
return ""
294+
}
295+
return requests.MessageSend{
296+
From: getOption("from"),
297+
To: getOption("to"),
298+
Content: getOption("message"),
299+
SIM: entities.SIMDefault,
300+
}
301+
}
302+
303+
func (h *DiscordHandler) sendSMS(ctx context.Context, c *fiber.Ctx, payload map[string]any) error {
304+
_, span, ctxLogger := h.tracer.StartWithLogger(ctx, h.logger)
305+
defer span.End()
306+
307+
discord, err := h.service.GetByServerID(ctx, payload["guild_id"].(string))
308+
if err != nil {
309+
msg := fmt.Sprintf("cannot get discord integration by server ID [%s]", payload["guild_id"].(string))
310+
ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
268311
return c.JSON(
269312
fiber.Map{
270313
"type": 4,
271-
//"data": fiber.Map{
272-
// "content": "✔ sending sms*",
273-
//},
274314
"data": fiber.Map{
275-
"content": "*⚠ could not send SMS message*",
315+
"content": "**⚠️ error while sending message**",
276316
"embeds": []fiber.Map{
277317
{
278-
"title": "The to field is not a valid phone number",
318+
"title": "We cannot find the link to your discord server to an account on [httpsms.com](https://httpsms.com/settings).",
279319
"color": 14681092,
280320
},
321+
},
322+
},
323+
},
324+
)
325+
}
326+
327+
request := h.createRequest(payload)
328+
messageEmbed := fiber.Map{
329+
"fields": []fiber.Map{
330+
{
331+
"name": "From:",
332+
"value": request.From,
333+
"inline": true,
334+
},
335+
{
336+
"name": "To:",
337+
"value": request.To,
338+
"inline": true,
339+
},
340+
{
341+
"name": "Content:",
342+
"value": request.Content,
343+
},
344+
},
345+
}
346+
347+
if errors := h.messageValidator.ValidateMessageSend(ctx, discord.UserID, request.Sanitize()); len(errors) != 0 {
348+
msg := fmt.Sprintf("validation errors [%s], while sending payload [%s]", spew.Sdump(errors), c.Body())
349+
ctxLogger.Warn(stacktrace.NewError(msg))
350+
351+
var embeds []fiber.Map
352+
for _, value := range errors {
353+
embeds = append(embeds, fiber.Map{
354+
"title": value[0],
355+
"color": 14681092,
356+
})
357+
}
358+
359+
return c.JSON(
360+
fiber.Map{
361+
"type": 4,
362+
"data": fiber.Map{
363+
"content": "**⚠️ error while sending message**",
364+
"embeds": append(embeds, messageEmbed),
365+
},
366+
},
367+
)
368+
}
369+
370+
if msg := h.billingService.IsEntitled(ctx, discord.UserID); msg != nil {
371+
ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] can't send a message", discord.UserID)))
372+
return c.JSON(
373+
fiber.Map{
374+
"type": 4,
375+
"data": fiber.Map{
376+
"content": "**⚠️ error while sending message**",
377+
"embeds": append([]fiber.Map{
281378
{
282-
"title": "The from field is not a valid phone number",
379+
"title": msg,
283380
"color": 14681092,
284381
},
382+
}, messageEmbed),
383+
},
384+
},
385+
)
386+
}
387+
388+
message, err := h.messageService.SendMessage(ctx, request.ToMessageSendParams(discord.UserID, c.OriginalURL()))
389+
if err != nil {
390+
msg := fmt.Sprintf("cannot send message with paylod [%s] from discord server [%s]", c.Body(), discord.ServerID)
391+
ctxLogger.Error(stacktrace.Propagate(err, msg))
392+
return c.JSON(
393+
fiber.Map{
394+
"type": 4,
395+
"data": fiber.Map{
396+
"content": "**Could not send the message⚠️**",
397+
"embeds": append([]fiber.Map{
285398
{
286-
"fields": []fiber.Map{
287-
{
288-
"name": "From:",
289-
"value": "+37259139660",
290-
"inline": true,
291-
},
292-
{
293-
"name": "To:",
294-
"value": "+37259139661",
295-
"inline": true,
296-
},
297-
{
298-
"name": "Content:",
299-
"value": "Hello World",
300-
},
301-
},
399+
"title": "Internal server error while sending SMS. Please try again later or contact support.",
400+
"color": 14681092,
302401
},
303-
},
402+
}, messageEmbed),
304403
},
305404
},
306405
)
307406
}
308407

309-
return h.responseBadRequest(c, stacktrace.NewError(fmt.Sprintf("unknown type [%d]", payload["type"])))
408+
messageEmbed["fields"] = append(messageEmbed["fields"].([]fiber.Map), fiber.Map{
409+
"name": "MessageID:",
410+
"value": message.ID,
411+
})
412+
return c.JSON(
413+
fiber.Map{
414+
"type": 4,
415+
"data": fiber.Map{
416+
"content": "✔ sending sms",
417+
"embeds": []fiber.Map{messageEmbed},
418+
},
419+
},
420+
)
310421
}
311422

312423
// verifyInteraction implements message verification of the discord interactions api

0 commit comments

Comments
 (0)