Skip to content

Commit

Permalink
Merge pull request #6 from junho100/dis
Browse files Browse the repository at this point in the history
Dis
  • Loading branch information
junho100 authored Dec 2, 2024
2 parents a5c96bc + 3301370 commit 370d717
Show file tree
Hide file tree
Showing 10 changed files with 222 additions and 35 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.13.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
go.opencensus.io v0.24.0 // indirect
Expand All @@ -40,6 +41,7 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/PuerkitoBio/goquery v1.10.0
github.com/bwmarrin/discordgo v0.28.1
github.com/bytedance/sonic v1.12.3 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbav
github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/bwmarrin/discordgo v0.28.1 h1:gXsuo2GBO7NbR6uqmrrBDplPUx2T3nzu775q/Rd1aG4=
github.com/bwmarrin/discordgo v0.28.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU=
github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
Expand Down Expand Up @@ -100,6 +102,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gT
github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA=
github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
Expand Down Expand Up @@ -163,6 +167,7 @@ golang.org/x/arch v0.11.0 h1:KXV8WWKCXm6tRpLirl2szsO5j/oOODwZf4hATmGVNs4=
golang.org/x/arch v0.11.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
Expand Down
5 changes: 5 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ func NewConfig() *Config {
DB: 0,
})

// Discord 봇 토큰 검증
if os.Getenv("DISCORD_BOT_TOKEN") == "" {
log.Fatal("DISCORD_BOT_TOKEN이 설정되지 않았습니다")
}

return &Config{
DB: db,
YoutubeService: service,
Expand Down
11 changes: 6 additions & 5 deletions internal/domain/entity/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ package entity
import "time"

type NotificationConfig struct {
ID uint `gorm:"primary_key"`
UserID string
User User
TelegramToken string `gorm:"telegram_token"`
TelegramChatID string `gorm:"telegram_chat_id"`
ID uint `gorm:"primary_key"`
UserID string
User User
TelegramToken string `gorm:"telegram_token"`
TelegramChatID string `gorm:"telegram_chat_id"`
DiscordClientID string `gorm:"discord_client_id"`
}

type NotificationKeyword struct {
Expand Down
31 changes: 21 additions & 10 deletions internal/http/dto/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,29 @@ type ValidateChatIDResponse struct {
}

type CreateNotificationConfigRequest struct {
Token string `json:"token"`
ChatID string `json:"chat_id"`
Keywords []string `json:"keywords"`
Token string `json:"token"`
ChatID string `json:"chat_id"`
Keywords []string `json:"keywords"`
DiscordClientID string `json:"discord_client_id"`
}

type CreateNotificationConfigDto struct {
UserID string
Token string
ChatID string
Keywords []string
UserID string
Token string
ChatID string
Keywords []string
DiscordClientID string
}

type CreateNotificationConfigResponse struct {
ConfigID uint `json:"config_id"`
}

type GetNotificationConfigResponse struct {
Token string `json:"token"`
ChatID string `json:"chat_id"`
Keywords []string `json:"keywords"`
Token string `json:"token"`
ChatID string `json:"chat_id"`
Keywords []string `json:"keywords"`
DiscordClientID string `json:"discord_client_id"`
}

type SendNotificationMessageDto struct {
Expand All @@ -65,3 +68,11 @@ type GetNotificationByIDResponse struct {
Content string `json:"content"`
Url string `json:"url"`
}

type ValidateDiscordClientIDRequest struct {
ClientID string `json:"client_id"`
}

type ValidateDiscordClientIDResponse struct {
IsValid bool `json:"is_valid"`
}
40 changes: 32 additions & 8 deletions internal/http/handler/notification.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"chee-go-backend/internal/common"
"chee-go-backend/internal/domain/service"
"chee-go-backend/internal/http/dto"
"chee-go-backend/internal/infrastructure/discord"
"chee-go-backend/internal/infrastructure/telegram"
"net/http"

Expand All @@ -12,13 +13,16 @@ import (

type NotificationHandler struct {
telegramClient telegram.TelegramClient
discordClient discord.DiscordClient
userService service.UserService
notificationService service.NotificationService
}

func NewNotificationHandler(router *gin.Engine, telegramClient telegram.TelegramClient, userService service.UserService, notificationService service.NotificationService) {
func NewNotificationHandler(router *gin.Engine, telegramClient telegram.TelegramClient, discordClient discord.DiscordClient, userService service.UserService, notificationService service.NotificationService) {
handler := &NotificationHandler{

telegramClient: telegramClient,
discordClient: discordClient,
userService: userService,
notificationService: notificationService,
}
Expand All @@ -28,6 +32,7 @@ func NewNotificationHandler(router *gin.Engine, telegramClient telegram.Telegram
router.POST("/api/notifications/config", handler.CreateNotificationConfig)
router.GET("/api/notifications/config", handler.GetNotificationConfig)
router.GET("/api/notifications/:id", handler.GetNotificationByID)
router.POST("/api/notifications/validate-discord-client-id", handler.ValidateDiscordClientID)
}

func (h *NotificationHandler) ValidateToken(c *gin.Context) {
Expand Down Expand Up @@ -96,10 +101,11 @@ func (h *NotificationHandler) CreateNotificationConfig(c *gin.Context) {
}

createNotificationDto := dto.CreateNotificationConfigDto{
UserID: userID,
Token: createNotificationConfigRequest.Token,
ChatID: createNotificationConfigRequest.ChatID,
Keywords: createNotificationConfigRequest.Keywords,
UserID: userID,
Token: createNotificationConfigRequest.Token,
ChatID: createNotificationConfigRequest.ChatID,
Keywords: createNotificationConfigRequest.Keywords,
DiscordClientID: createNotificationConfigRequest.DiscordClientID,
}
if configID, err = h.notificationService.CreateNotificationConfig(createNotificationDto); err != nil {
response := &common.CommonErrorResponse{
Expand Down Expand Up @@ -147,9 +153,10 @@ func (h *NotificationHandler) GetNotificationConfig(c *gin.Context) {
keywords := h.notificationService.GetKeywordsByNotificationID(notificationConfig.ID)

getNotificationConfigResponse := dto.GetNotificationConfigResponse{
Token: notificationConfig.TelegramToken,
ChatID: notificationConfig.TelegramChatID,
Keywords: keywords,
Token: notificationConfig.TelegramToken,
ChatID: notificationConfig.TelegramChatID,
Keywords: keywords,
DiscordClientID: notificationConfig.DiscordClientID,
}
c.JSON(http.StatusOK, getNotificationConfigResponse)
}
Expand Down Expand Up @@ -177,3 +184,20 @@ func (h *NotificationHandler) GetNotificationByID(c *gin.Context) {
response := dto.GetNotificationByIDResponse(*notification)
c.JSON(http.StatusOK, response)
}

func (h *NotificationHandler) ValidateDiscordClientID(c *gin.Context) {
var validateDiscordClientIDRequest dto.ValidateDiscordClientIDRequest

if err := c.BindJSON(&validateDiscordClientIDRequest); err != nil {
response := &common.CommonErrorResponse{
Message: "bad content.",
}
c.JSON(http.StatusUnprocessableEntity, response)
return
}

validateDiscordClientIDResponse := dto.ValidateDiscordClientIDResponse{
IsValid: h.discordClient.ValidateClientID(validateDiscordClientIDRequest.ClientID),
}
c.JSON(http.StatusCreated, validateDiscordClientIDResponse)
}
41 changes: 30 additions & 11 deletions internal/infrastructure/cron/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"chee-go-backend/internal/domain/service"
"chee-go-backend/internal/http/dto"
"chee-go-backend/internal/infrastructure/crawler"
"chee-go-backend/internal/infrastructure/discord"
"chee-go-backend/internal/infrastructure/redis"
"chee-go-backend/internal/infrastructure/telegram"
"fmt"
Expand All @@ -22,11 +23,12 @@ type CronJob struct {
cron *cron.Cron
notificationService service.NotificationService
telegramClient telegram.TelegramClient
discordClient discord.DiscordClient
notificationStatus redis.NotificationStatus
crawler crawler.Crawler
}

func NewCronJob(notificationService service.NotificationService, telegramClient telegram.TelegramClient, notificationStatus redis.NotificationStatus, crawler crawler.Crawler) *CronJob {
func NewCronJob(notificationService service.NotificationService, telegramClient telegram.TelegramClient, discordClient discord.DiscordClient, notificationStatus redis.NotificationStatus, crawler crawler.Crawler) *CronJob {
// UTC+9 시간으로 고정 설정
loc := time.FixedZone("KST", 9*60*60) // UTC+9 시간

Expand All @@ -35,6 +37,7 @@ func NewCronJob(notificationService service.NotificationService, telegramClient
cron: c,
notificationService: notificationService,
telegramClient: telegramClient,
discordClient: discordClient,
notificationStatus: notificationStatus,
crawler: crawler,
}
Expand Down Expand Up @@ -112,21 +115,37 @@ func (c *CronJob) Start() {
} else {
url = os.Getenv("CLIENT_BASE_URL")
}
noticeUrl := fmt.Sprintf("%s/notification/%s", url, notice.ID)

// Telegram 알림 전송 (기존 토큰이 있는 경우)
if config.TelegramToken != "" && config.TelegramChatID != "" {
sendNotificationMessageDto := dto.SendNotificationMessageDto{
Title: notice.Title,
Date: notice.Date,
Url: noticeUrl,
Token: config.TelegramToken,
ChatID: config.TelegramChatID,
}

sendNotificationMessageDto := dto.SendNotificationMessageDto{
Title: notice.Title,
Date: notice.Date,
Url: fmt.Sprintf("%s/notification/%s", url, notice.ID),
Token: config.TelegramToken,
ChatID: config.TelegramChatID,
if err := c.telegramClient.SendNotificationMessage(sendNotificationMessageDto); err != nil {
log.Printf("텔레그램 알림 전송 실패 (사용자: %s, 공지: %s): %v", config.UserID, notice.ID, err)
}
}

if err := c.telegramClient.SendNotificationMessage(sendNotificationMessageDto); err != nil {
log.Printf("알림 전송 실패 (사용자: %s, 공지: %s): %v", config.UserID, notice.ID, err)
continue
// Discord 알림 전송 (Discord Client ID가 있는 경우)
if config.DiscordClientID != "" {
if err := c.discordClient.SendNotificationMessage(
config.DiscordClientID,
notice.Title,
noticeUrl,
notice.Date,
); err != nil {
log.Printf("Discord 알림 전송 실패 (사용자: %s, 공지: %s): %v", config.UserID, notice.ID, err)
continue
}
}

// 5. 알림 처리 상태를 Redis에 저장
// 알림 처리 상태를 Redis에 저장
if err := c.notificationStatus.MarkAsProcessed(config.UserID, notice.ID); err != nil {
log.Printf("알림 상태 저장 실패 (사용자: %s, 공지: %s): %v", config.UserID, notice.ID, err)
}
Expand Down
111 changes: 111 additions & 0 deletions internal/infrastructure/discord/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package discord

import (
"context"
"fmt"
"log"
"os"
"sync"
"time"

"github.com/bwmarrin/discordgo"
)

type DiscordClient interface {
ValidateClientID(clientID string) bool
SendMessage(clientID string, message string) error
SendNotificationMessage(clientID string, title string, url string, date time.Time) error
}

type discordClient struct {
session *discordgo.Session
}

func (c *discordClient) ValidateClientID(clientID string) bool {
// DM 채널 생성 시도
channel, err := c.session.UserChannelCreate(clientID)
if err != nil {
log.Printf("DM 채널 생성 실패: %v", err)
return false
}

// 테스트 메시지 전송
testMessage := "[취Go 알림]\nDiscord Client ID 확인 중..."
_, err = c.session.ChannelMessageSend(channel.ID, testMessage)
if err != nil {
log.Printf("메시지 전송 실패: %v", err)
return false
}

return true
}

func (c *discordClient) SendMessage(clientID string, message string) error {
// DM 채널 생성
channel, err := c.session.UserChannelCreate(clientID)
if err != nil {
return fmt.Errorf("DM 채널 생성 실패: %v", err)
}

// 메시지 전송
_, err = c.session.ChannelMessageSend(channel.ID, message)
if err != nil {
return fmt.Errorf("메시지 전송 실패: %v", err)
}

return nil
}

func (c *discordClient) SendNotificationMessage(clientID string, title string, url string, date time.Time) error {
messageText := fmt.Sprintf("[취Go 알림]\n공지사항\n제목: %s\n링크: %s\n작성일: %s",
title,
url,
date.Format("2006-01-02"))

return c.SendMessage(clientID, messageText)
}

func NewDiscordClient() (DiscordClient, error) {
token := os.Getenv("DISCORD_BOT_TOKEN")
if token == "" {
return nil, fmt.Errorf("DISCORD_BOT_TOKEN이 설정되지 않았습니다")
}

discord, err := discordgo.New("Bot " + token)
if err != nil {
return nil, fmt.Errorf("Discord 세션 생성 실패: %v", err)
}

var wg sync.WaitGroup
wg.Add(1)

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

discord.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
log.Printf("Discord 봇 '%s' 준비 완료", s.State.User.Username)
wg.Done()
})

discord.Identify.Intents = discordgo.IntentsGuildMessages | discordgo.IntentsDirectMessages

if err := discord.Open(); err != nil {
return nil, fmt.Errorf("Discord 연결 실패: %v", err)
}

done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()

select {
case <-ctx.Done():
discord.Close()
return nil, fmt.Errorf("Discord 봇 준비 시간 초과 (30초)")
case <-done:
return &discordClient{
session: discord,
}, nil
}
}
Loading

0 comments on commit 370d717

Please sign in to comment.