From 73314aebbf556b65d44126dbe1abea6bd03b8853 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 29 Mar 2025 21:57:32 +0300 Subject: [PATCH 01/10] added: env and docker files --- Dockerfile | 21 +++++++++++++++++++++ secret.env | 11 +++++++++++ 2 files changed, 32 insertions(+) create mode 100644 Dockerfile create mode 100644 secret.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3565245 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM golang:1.24.1-alpine3.21 AS builder + +WORKDIR /build + +COPY go.* ./ + +RUN go mod tidy + +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -o app ./ + +FROM alpine:latest + +RUN mkdir -p /home + +COPY --from=builder /build/app /home/app + +CMD ["./home/app"] \ No newline at end of file diff --git a/secret.env b/secret.env new file mode 100644 index 0000000..5c9db77 --- /dev/null +++ b/secret.env @@ -0,0 +1,11 @@ +#APP +APP_LOG_LEVEL=info + +#Telegram +TELEGRAM_TOKEN= +TELEGRAM_CHANNEL_ID= + +#Discord +DISCORD_TOKEN= +GUILD_ID= +CHANNEL_ID= From 082afea891efb778d0b925b9711277e6d1fefa76 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 29 Mar 2025 21:58:04 +0300 Subject: [PATCH 02/10] small addition --- config/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index 9d621c2..116d96e 100644 --- a/config/config.go +++ b/config/config.go @@ -31,7 +31,7 @@ type DiscordConfig struct { func NewConfig() *Config { return &Config{ AppConfig{ - LogLvl: "info", + LogLvl: getEnv("APP_LOG_LEVEL", "info"), }, TelegramConfig{ Token: getEnv("TELEGRAM_TOKEN", ""), From 45405197536e1f58f6bf736f169257c79b78a030 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 29 Mar 2025 21:58:33 +0300 Subject: [PATCH 03/10] extended dto --- dto/discord.go | 1 + dto/image.go | 6 ++++++ dto/telegram.go | 1 + 3 files changed, 8 insertions(+) create mode 100644 dto/image.go diff --git a/dto/discord.go b/dto/discord.go index ade3dbf..3c56908 100644 --- a/dto/discord.go +++ b/dto/discord.go @@ -3,4 +3,5 @@ package dto type DiscordDTO struct { AuthorName string Content string + Images *[]Image } diff --git a/dto/image.go b/dto/image.go new file mode 100644 index 0000000..b4f4a8d --- /dev/null +++ b/dto/image.go @@ -0,0 +1,6 @@ +package dto + +type Image struct { + Data []byte + Filename string +} diff --git a/dto/telegram.go b/dto/telegram.go index 0fe71b2..946041a 100644 --- a/dto/telegram.go +++ b/dto/telegram.go @@ -3,4 +3,5 @@ package dto type TelegramDTO struct { AuthorName string Content string + Images *[]Image } From 9714464d2095c77999f7e627fe5b5443179ecaaa Mon Sep 17 00:00:00 2001 From: nquidox Date: Sat, 29 Mar 2025 22:00:58 +0300 Subject: [PATCH 04/10] added: image send --- discordBot/handler.go | 25 +++++- discordBot/messages.go | 47 +++++++++++ tgBot/handler.go | 171 +++++++++++++++++++++++++++++++++++------ 3 files changed, 218 insertions(+), 25 deletions(-) diff --git a/discordBot/handler.go b/discordBot/handler.go index 6c5c95d..1fa98ed 100644 --- a/discordBot/handler.go +++ b/discordBot/handler.go @@ -1,6 +1,7 @@ package discordBot import ( + "bytes" "context" "fmt" "github.com/disgoorg/disgo" @@ -18,11 +19,16 @@ type DiscordBot struct { client bot.Client guildID snowflake.ID channelID snowflake.ID + token string } func NewDiscordBot(ctx context.Context, config config.DiscordConfig) (*DiscordBot, error) { var err error - dsBot := &DiscordBot{ctx: ctx, channelID: config.ChannelID} + dsBot := &DiscordBot{ + ctx: ctx, + channelID: config.ChannelID, + token: config.Token, + } dsBot.client, err = disgo.New(config.Token, bot.WithGatewayConfigOpts( @@ -40,7 +46,7 @@ func NewDiscordBot(ctx context.Context, config config.DiscordConfig) (*DiscordBo return dsBot, nil } -func (d *DiscordBot) Start(fromTelegram chan dto.TelegramDTO) chan dto.DiscordDTO { +func (d *DiscordBot) Start(fromTelegram <-chan dto.TelegramDTO) chan dto.DiscordDTO { log.Info("Starting discord bot...") msgChan := make(chan dto.DiscordDTO, 100) @@ -50,11 +56,26 @@ func (d *DiscordBot) Start(fromTelegram chan dto.TelegramDTO) chan dto.DiscordDT for msg := range fromTelegram { log.WithField("content", msg).Debug("DS | Message from Telegram") + var files []*discord.File + for _, media := range *(msg.Images) { + file := &discord.File{ + Name: media.Filename, + Reader: bytes.NewReader(media.Data), + } + files = append(files, file) + } + m := discord.MessageCreate{Content: fmt.Sprintf("[%s]\n%s", msg.AuthorName, msg.Content)} + if len(files) > 0 { + m.Files = append(m.Files, files...) + + } + _, err := d.client.Rest().CreateMessage(d.channelID, m) if err != nil { log.Errorf("Failed to send message to Discord: %v", err) + continue } } }() diff --git a/discordBot/messages.go b/discordBot/messages.go index 0322a30..697082f 100644 --- a/discordBot/messages.go +++ b/discordBot/messages.go @@ -2,8 +2,11 @@ package discordBot import ( "github.com/disgoorg/disgo/bot" + "github.com/disgoorg/disgo/discord" "github.com/disgoorg/disgo/events" log "github.com/sirupsen/logrus" + "io" + "net/http" "tg-disc-bot/dto" ) @@ -14,9 +17,20 @@ type messageHandler struct { func (m *messageHandler) OnEvent(event bot.Event) { if e, ok := event.(*events.MessageCreate); ok { if !e.Message.Author.Bot { + attachments := e.Message.Attachments + var images *[]dto.Image + var err error + if len(attachments) > 0 { + images, err = getImages(attachments) + if err != nil { + log.Warnf("could not get images from attachments: %v", err) + } + } + message := dto.DiscordDTO{ AuthorName: e.Message.Author.Username, Content: e.Message.Content, + Images: images, } m.msgChan <- message @@ -26,3 +40,36 @@ func (m *messageHandler) OnEvent(event bot.Event) { } } + +func getImages(a []discord.Attachment) (*[]dto.Image, error) { + var images []dto.Image + for _, item := range a { + if (*item.ContentType == "image/png") || (*item.ContentType == "image/jpeg") { + imageData, err := downloadImage(item.URL) + if err != nil { + log.Errorf("Error downloading image: %s", err) + return nil, err + } + + images = append(images, dto.Image{ + Data: imageData, + Filename: item.Filename, + }) + } + } + return &images, nil +} + +func downloadImage(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return data, nil +} diff --git a/tgBot/handler.go b/tgBot/handler.go index ce963cf..40c52c0 100644 --- a/tgBot/handler.go +++ b/tgBot/handler.go @@ -1,23 +1,40 @@ package tgBot import ( + "bytes" "context" "fmt" "github.com/go-telegram/bot" "github.com/go-telegram/bot/models" log "github.com/sirupsen/logrus" + "io" + "net/http" + "path/filepath" + "sync" "tg-disc-bot/config" "tg-disc-bot/dto" + "time" ) type TgBot struct { - ctx context.Context - bot *bot.Bot - chatID int64 + ctx context.Context + bot *bot.Bot + chatID int64 + token string + msgChan chan dto.TelegramDTO + mediaGroupCache map[string][]dto.Image + cacheMutex sync.Mutex } func NewTgBot(ctx context.Context, config config.TelegramConfig) (*TgBot, error) { - tgBot := &TgBot{ctx: ctx, chatID: config.ChatID} + tgBot := &TgBot{ + ctx: ctx, + chatID: config.ChatID, + token: config.Token, + msgChan: make(chan dto.TelegramDTO, 100), + mediaGroupCache: make(map[string][]dto.Image, 10), + cacheMutex: sync.Mutex{}, + } var err error tgBot.bot, err = bot.New(config.Token) @@ -30,24 +47,7 @@ func NewTgBot(ctx context.Context, config config.TelegramConfig) (*TgBot, error) func (b *TgBot) Start(fromDiscord chan dto.DiscordDTO) chan dto.TelegramDTO { - msgChan := make(chan dto.TelegramDTO, 100) - - b.bot.RegisterHandler( - bot.HandlerTypeMessageText, - "", - bot.MatchTypeContains, - func(ctx context.Context, bt *bot.Bot, update *models.Update) { - if update.Message != nil || !update.Message.From.IsBot { - msg := dto.TelegramDTO{ - AuthorName: update.Message.From.Username, - Content: update.Message.Text, - } - - fmt.Println(update.Message.Chat.ID) - - msgChan <- msg - } - }) + b.bot.RegisterHandler(bot.HandlerTypeMessageText, "", bot.MatchTypeContains, b.mainHandler) go func() { log.Info("Starting telegram bot...") @@ -63,8 +63,133 @@ func (b *TgBot) Start(fromDiscord chan dto.DiscordDTO) chan dto.TelegramDTO { ChatID: b.chatID, Text: m, }) + + if msg.Images != nil { + var mediaGroup []models.InputMedia + for _, img := range *msg.Images { + photo := &models.InputMediaPhoto{ + Media: fmt.Sprintf("attach://%s", img.Filename), + Caption: "test caption", + MediaAttachment: bytes.NewReader(img.Data), + } + mediaGroup = append(mediaGroup, photo) + } + + _, err := b.bot.SendMediaGroup(b.ctx, &bot.SendMediaGroupParams{ + ChatID: b.chatID, + Media: mediaGroup, + }) + + if err != nil { + log.Error("TG | Failed to send media group") + } + } } }() - return msgChan + return b.msgChan +} + +func (b *TgBot) mainHandler(ctx context.Context, bt *bot.Bot, update *models.Update) { + if update.Message != nil && !update.Message.From.IsBot { + mediaGroupID := update.Message.MediaGroupID + if mediaGroupID != "" { + b.processMediaGroup(update, mediaGroupID, b.msgChan) + } else { + b.proccessSimple(update, b.msgChan) + } + } +} + +func (b *TgBot) getImage(photo models.PhotoSize) *dto.Image { + fileID := &bot.GetFileParams{FileID: photo.FileID} + file, err := b.bot.GetFile(b.ctx, fileID) + if err != nil { + log.Errorf("GetFile error: %s", err) + return nil + } + + if file.FilePath == "" { + log.Errorf("FilePath is empty for fileID: %s", photo.FileID) + return nil + } + + resp, err := http.Get(fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", b.token, file.FilePath)) + if err != nil { + log.Errorf("Download error: %s", err) + return nil + } + defer resp.Body.Close() + + imgBytes, err := io.ReadAll(resp.Body) + if err != nil { + log.Errorf("ReadAll error: %s", err) + return nil + } + + image := dto.Image{ + Filename: filepath.Base(file.FilePath), + Data: imgBytes, + } + + return &image +} + +func (b *TgBot) proccessSimple(update *models.Update, msgChan chan<- dto.TelegramDTO) { + var images []dto.Image + content := update.Message.Text + + if update.Message.Photo != nil { + largest := update.Message.Photo[len(update.Message.Photo)-1] + images = append(images, *b.getImage(largest)) + content = update.Message.Caption + } + + msg := dto.TelegramDTO{ + AuthorName: update.Message.From.Username, + Content: content, + Images: &images, + } + + msgChan <- msg +} + +func (b *TgBot) processMediaGroup(update *models.Update, mediaGroupID string, msgChan chan<- dto.TelegramDTO) { + b.cacheMutex.Lock() + defer b.cacheMutex.Unlock() + + if update.Message.Photo != nil { + largest := update.Message.Photo[len(update.Message.Photo)-1] + image := b.getImage(largest) + b.mediaGroupCache[mediaGroupID] = append(b.mediaGroupCache[mediaGroupID], *image) + } + + var content string + if update.Message.Caption != "" { + content = update.Message.Caption + } else { + content = update.Message.Text + } + + go func() { + time.Sleep(2 * time.Second) + + b.cacheMutex.Lock() + defer b.cacheMutex.Unlock() + + images, exists := b.mediaGroupCache[mediaGroupID] + if !exists || len(images) == 0 { + return + } + + delete(b.mediaGroupCache, mediaGroupID) + + msg := dto.TelegramDTO{ + AuthorName: update.Message.From.Username, + Content: content, + Images: &images, + } + + msgChan <- msg + }() } From 724169c09cfcdda5a638a5ee40f12abd216d00b8 Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 5 Mar 2026 16:19:00 +0300 Subject: [PATCH 05/10] update --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 485dee6..7a6532f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .idea +config/dev_config.go From d9288d2b5582256533fd486be8b83a9cf94a6b60 Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 5 Mar 2026 16:19:46 +0300 Subject: [PATCH 06/10] update --- go.mod | 42 +++++++++++++++++++++-- go.sum | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 137 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index f369d5a..8d14ef0 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,54 @@ module tg-disc-bot -go 1.24.1 +go 1.25.0 require ( github.com/disgoorg/disgo v0.18.15 github.com/disgoorg/snowflake/v2 v2.0.3 + github.com/gin-gonic/gin v1.12.0 github.com/go-telegram/bot v1.14.1 github.com/sirupsen/logrus v1.9.3 + github.com/zsais/go-gin-prometheus v1.0.3 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect github.com/disgoorg/json v1.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect github.com/gorilla/websocket v1.5.3 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/prometheus/client_golang v1.22.0 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/sys v0.31.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect ) diff --git a/go.sum b/go.sum index 7eb1b36..9ddb817 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,15 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -7,25 +19,106 @@ github.com/disgoorg/json v1.2.0 h1:6e/j4BCfSHIvucG1cd7tJPAOp1RgnnMFSqkvZUtEd1Y= github.com/disgoorg/json v1.2.0/go.mod h1:BHDwdde0rpQFDVsRLKhma6Y7fTbQKub/zdGO5O9NqqA= github.com/disgoorg/snowflake/v2 v2.0.3 h1:3B+PpFjr7j4ad7oeJu4RlQ+nYOTadsKapJIzgvSI2Ro= github.com/disgoorg/snowflake/v2 v2.0.3/go.mod h1:W6r7NUA7DwfZLwr00km6G4UnZ0zcoLBRufhkFWgAc4c= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-telegram/bot v1.14.1 h1:ySVCITvYsvBSiChOmr6GolLUcWX2T/ugykc2rjIaaQg= github.com/go-telegram/bot v1.14.1/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad h1:qIQkSlF5vAUHxEmTbaqt1hkJ/t6skqEGYiMag343ucI= github.com/sasha-s/go-csync v0.0.0-20240107134140-fcbab37b09ad/go.mod h1:/pA7k3zsXKdjjAiUhB5CjuKib9KJGCaLvZwtxGC8U0s= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/zsais/go-gin-prometheus v1.0.3 h1:NIYXItaoGNiyDWXqrIzfQHWcRnen+iwgAw4sX/UieiM= +github.com/zsais/go-gin-prometheus v1.0.3/go.mod h1:avQI7yOKIhpOi4QJxFZdmZb47AEjmS4MTC4Z6PsNmiA= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From fd7868128afdc51dd8e2a2e8ceec7a909173378d Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 5 Mar 2026 16:19:55 +0300 Subject: [PATCH 07/10] prometheus metrics --- router/handler.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 router/handler.go diff --git a/router/handler.go b/router/handler.go new file mode 100644 index 0000000..5ede9de --- /dev/null +++ b/router/handler.go @@ -0,0 +1,67 @@ +package router + +import ( + "context" + "errors" + "github.com/gin-gonic/gin" + log "github.com/sirupsen/logrus" + ginprometheus "github.com/zsais/go-gin-prometheus" + "net/http" +) + +type Handler struct { + srv *http.Server +} + +type Deps struct { + Addr string + GinMode string +} + +const pkgLogHeader string = "Router |" + +func NewHandler(deps Deps) *Handler { + engine := gin.Default() + + if deps.GinMode == "release" { + gin.SetMode(gin.ReleaseMode) + err := engine.SetTrustedProxies([]string{"172.20.0.0/16"}) + if err != nil { + log.WithError(err).Errorf("%v Set proxies failed", pkgLogHeader) + return nil + } + } + + engine.GET("/", func(c *gin.Context) { c.JSON(200, gin.H{"msg": "v2"}) }) + + p := ginprometheus.NewPrometheus("gin") + p.Use(engine) + + engine.Use(gin.LoggerWithConfig(gin.LoggerConfig{ + Skip: func(c *gin.Context) bool { + return c.Request.URL.Path == "/metrics" + }, + })) + + srv := http.Server{ + Addr: deps.Addr, + Handler: engine, + } + + return &Handler{ + srv: &srv, + } +} + +func (h *Handler) Run() error { + log.Infof("Starting server on %s", h.srv.Addr) + if err := h.srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.WithError(err).Errorf("%v ListenAndServe failed", pkgLogHeader) + return err + } + return nil +} + +func (h *Handler) Shutdown(ctx context.Context) error { + return h.srv.Shutdown(ctx) +} From 5f72059bb641681217a5f157e400479c134ac6c4 Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 5 Mar 2026 16:20:28 +0300 Subject: [PATCH 08/10] format change --- discordBot/handler.go | 2 +- tgBot/handler.go | 29 ++++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/discordBot/handler.go b/discordBot/handler.go index 1fa98ed..f3c2e7f 100644 --- a/discordBot/handler.go +++ b/discordBot/handler.go @@ -65,7 +65,7 @@ func (d *DiscordBot) Start(fromTelegram <-chan dto.TelegramDTO) chan dto.Discord files = append(files, file) } - m := discord.MessageCreate{Content: fmt.Sprintf("[%s]\n%s", msg.AuthorName, msg.Content)} + m := discord.MessageCreate{Content: fmt.Sprintf("**[%s]**\n%s", msg.AuthorName, msg.Content)} if len(files) > 0 { m.Files = append(m.Files, files...) diff --git a/tgBot/handler.go b/tgBot/handler.go index 40c52c0..e086439 100644 --- a/tgBot/handler.go +++ b/tgBot/handler.go @@ -57,19 +57,21 @@ func (b *TgBot) Start(fromDiscord chan dto.DiscordDTO) chan dto.TelegramDTO { go func() { for msg := range fromDiscord { log.WithField("content", msg).Debug("TG | Message from Discord") - m := fmt.Sprintf("[%s]\n%s", msg.AuthorName, msg.Content) + + m := fmt.Sprintf("*\\[ %s \\]*\n%s", msg.AuthorName, msg.Content) b.bot.SendMessage(b.ctx, &bot.SendMessageParams{ - ChatID: b.chatID, - Text: m, + ChatID: b.chatID, + Text: m, + ParseMode: "", }) if msg.Images != nil { var mediaGroup []models.InputMedia for _, img := range *msg.Images { photo := &models.InputMediaPhoto{ - Media: fmt.Sprintf("attach://%s", img.Filename), - Caption: "test caption", + Media: fmt.Sprintf("attach://%s", img.Filename), + //Caption: "test caption", MediaAttachment: bytes.NewReader(img.Data), } mediaGroup = append(mediaGroup, photo) @@ -146,7 +148,7 @@ func (b *TgBot) proccessSimple(update *models.Update, msgChan chan<- dto.Telegra } msg := dto.TelegramDTO{ - AuthorName: update.Message.From.Username, + AuthorName: b.getName(update), Content: content, Images: &images, } @@ -185,7 +187,7 @@ func (b *TgBot) processMediaGroup(update *models.Update, mediaGroupID string, ms delete(b.mediaGroupCache, mediaGroupID) msg := dto.TelegramDTO{ - AuthorName: update.Message.From.Username, + AuthorName: b.getName(update), Content: content, Images: &images, } @@ -193,3 +195,16 @@ func (b *TgBot) processMediaGroup(update *models.Update, mediaGroupID string, ms msgChan <- msg }() } + +func (b *TgBot) getName(update *models.Update) string { + switch { + case update.Message.From.Username != "": + return update.Message.From.Username + case update.Message.From.FirstName != "": + return update.Message.From.FirstName + case update.Message.From.LastName != "": + return update.Message.From.LastName + default: + return "No name" + } +} From fb7f88ff7e814b6968e5e73bbf569a8d3134b66c Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 5 Mar 2026 16:20:47 +0300 Subject: [PATCH 09/10] conf for gin engine --- config/config.go | 22 ++++++++++++++++++---- main.go | 23 +++++++++++++++++++++++ secret.env | 3 +++ 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 116d96e..3e5f097 100644 --- a/config/config.go +++ b/config/config.go @@ -8,9 +8,10 @@ import ( ) type Config struct { - AppConf AppConfig - TgConf TelegramConfig - DsConf DiscordConfig + AppConf AppConfig + TgConf TelegramConfig + DsConf DiscordConfig + HttpConf HttpConfig } type AppConfig struct { @@ -28,10 +29,17 @@ type DiscordConfig struct { ChannelID snowflake.ID } +type HttpConfig struct { + Host string + Port string + GinMode string +} + +// prod config func NewConfig() *Config { return &Config{ AppConfig{ - LogLvl: getEnv("APP_LOG_LEVEL", "info"), + LogLvl: getEnv("APP_LOG_LEVEL", "debug"), }, TelegramConfig{ Token: getEnv("TELEGRAM_TOKEN", ""), @@ -42,6 +50,12 @@ func NewConfig() *Config { GuildID: convertID(getEnv("GUILD_ID", "")), ChannelID: convertID(getEnv("CHANNEL_ID", "")), }, + + HttpConfig{ + Host: getEnv("HTTP_HOST", "0.0.0.0"), + Port: getEnv("HTTP_PORT", "8080"), + GinMode: getEnv("GIN_MODE", "debug"), + }, } } diff --git a/main.go b/main.go index 17df0dd..0a2d0f1 100644 --- a/main.go +++ b/main.go @@ -3,13 +3,16 @@ package main import ( "context" log "github.com/sirupsen/logrus" + "net" "os" "os/signal" "syscall" "tg-disc-bot/config" "tg-disc-bot/discordBot" "tg-disc-bot/dto" + "tg-disc-bot/router" "tg-disc-bot/tgBot" + "time" ) func main() { @@ -38,13 +41,33 @@ func main() { tgMsgs := tgb.Start(fromDiscord) dsMsgs := dsb.Start(fromTelegram) + r := router.NewHandler(router.Deps{ + Addr: net.JoinHostPort(c.HttpConf.Host, c.HttpConf.Port), + GinMode: c.HttpConf.GinMode, + }) + log.Info("App is now running. Press CTRL-C to exit.") + errChan := make(chan error, 10) + + go func() { + if err = r.Run(); err != nil { + errChan <- err + } + }() + for { select { case sig := <-shutdown: { + shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 15*time.Second) + + if err = r.Shutdown(shutdownCtx); err != nil { + log.WithError(err).Error("Error shutting down router") + } + log.WithField("type", sig).Info("terminating, close app") + shutdownCancel() os.Exit(0) } case <-ctx.Done(): diff --git a/secret.env b/secret.env index 5c9db77..987788d 100644 --- a/secret.env +++ b/secret.env @@ -1,5 +1,8 @@ #APP APP_LOG_LEVEL=info +HTTP_HOST=0.0.0.0 +HTTP_PORT=8080 +GIN_MODE=release #Telegram TELEGRAM_TOKEN= From d9e56de17f1374b0bfc05bde6c3947157c368731 Mon Sep 17 00:00:00 2001 From: nquidox Date: Thu, 5 Mar 2026 16:24:34 +0300 Subject: [PATCH 10/10] version up --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3565245..56a70f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.1-alpine3.21 AS builder +FROM golang:1.25.7-alpine3.23 AS builder WORKDIR /build