From e14dbfb741c9e7d675d005a4a2de28cddea2fe9a Mon Sep 17 00:00:00 2001 From: Nikolay Ushmodin Date: Wed, 22 Oct 2025 14:35:54 +0700 Subject: [PATCH 1/3] Migration to github.com/go-telegram/bot --- go.mod | 3 +- go.sum | 6 +- plugin.go | 172 ++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 118 insertions(+), 63 deletions(-) diff --git a/go.mod b/go.mod index 5d059de..19a5920 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.22 require ( github.com/appleboy/drone-template-lib v1.3.0 - github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible + github.com/go-telegram/bot v1.17.0 github.com/joho/godotenv v1.5.1 github.com/stretchr/testify v1.9.0 github.com/urfave/cli v1.22.15 @@ -27,7 +27,6 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/technoweenie/multipartstreamer v1.0.1 // indirect golang.org/x/crypto v0.27.0 // indirect golang.org/x/sys v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ed521a9..0565c61 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ 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= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= +github.com/go-telegram/bot v1.17.0 h1:Hs0kGxSj97QFqOQP0zxduY/4tSx8QDzvNI9uVRS+zmY= +github.com/go-telegram/bot v1.17.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -60,8 +60,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= diff --git a/plugin.go b/plugin.go index 5bbd146..c17e64d 100644 --- a/plugin.go +++ b/plugin.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "context" "encoding/json" "errors" "fmt" @@ -14,9 +15,11 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/appleboy/drone-template-lib/template" - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" + tgbotapi "github.com/go-telegram/bot" + "github.com/go-telegram/bot/models" ) const ( @@ -308,25 +311,11 @@ func (p Plugin) Exec() (err error) { } } - var proxyURL *url.URL - if proxyURL, err = url.Parse(p.Config.Socks5); err != nil { - return fmt.Errorf("unable to unmarshall socks5 proxy url from string '%s': %v", p.Config.Socks5, err) - } - - var bot *tgbotapi.BotAPI - if len(p.Config.Socks5) > 0 { - proxyClient := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}} - bot, err = tgbotapi.NewBotAPIWithClient(p.Config.Token, proxyClient) - } else { - bot, err = tgbotapi.NewBotAPI(p.Config.Token) - } - + bot, err := p.newBot() if err != nil { return err } - bot.Debug = p.Config.Debug - ids := parseTo(p.Config.To, p.Commit.Email, p.Config.MatchEmail) photos := globList(trimElement(p.Config.Photo)) documents := globList(trimElement(p.Config.Document)) @@ -356,6 +345,7 @@ func (p Plugin) Exec() (err error) { p.Repo.Name = escapeMarkdownOne(p.Repo.Name) } + background := context.Background() // send message. for _, user := range ids { for _, value := range message { @@ -365,56 +355,112 @@ func (p Plugin) Exec() (err error) { } txt = html.UnescapeString(txt) - - msg := tgbotapi.NewMessage(user, txt) - msg.ParseMode = p.Config.Format - msg.DisableWebPagePreview = p.Config.DisableWebPagePreview - msg.DisableNotification = p.Config.DisableNotification - if err := p.Send(bot, msg); err != nil { + _, err = bot.SendMessage(background, &tgbotapi.SendMessageParams{ + ChatID: user, + Text: txt, + ParseMode: models.ParseMode(p.Config.Format), + LinkPreviewOptions: &models.LinkPreviewOptions{ + IsDisabled: &p.Config.DisableWebPagePreview, + }, + DisableNotification: p.Config.DisableNotification, + }) + if err != nil { return err } } for _, value := range photos { - msg := tgbotapi.NewPhotoUpload(user, value) - if err := p.Send(bot, msg); err != nil { + f, err := os.Open(value) + if err != nil { + return err + } + defer f.Close() + _, err = bot.SendPhoto(background, &tgbotapi.SendPhotoParams{ + ChatID: user, + Photo: &models.InputFileUpload{Filename: value, Data: f}, + }) + if err != nil { return err } } for _, value := range documents { - msg := tgbotapi.NewDocumentUpload(user, value) - if err := p.Send(bot, msg); err != nil { + f, err := os.Open(value) + if err != nil { return err } + defer f.Close() + _, err = bot.SendDocument(background, &tgbotapi.SendDocumentParams{ + ChatID: user, + Document: &models.InputFileUpload{Filename: value, Data: f}, + DisableNotification: p.Config.DisableNotification, + }) + if err != nil { + return nil + } } for _, value := range stickers { - msg := tgbotapi.NewStickerUpload(user, value) - if err := p.Send(bot, msg); err != nil { + f, err := os.Open(value) + if err != nil { + return err + } + defer f.Close() + _, err = bot.SendSticker(background, &tgbotapi.SendStickerParams{ + ChatID: user, + Sticker: &models.InputFileUpload{Filename: value, Data: f}, + DisableNotification: p.Config.DisableNotification, + }) + if err != nil { return err } } for _, value := range audios { - msg := tgbotapi.NewAudioUpload(user, value) - msg.Title = "Audio Message." - if err := p.Send(bot, msg); err != nil { + f, err := os.Open(value) + if err != nil { + return err + } + defer f.Close() + _, err = bot.SendAudio(background, &tgbotapi.SendAudioParams{ + ChatID: user, + Audio: &models.InputFileUpload{Filename: value, Data: f}, + DisableNotification: p.Config.DisableNotification, + }) + if err != nil { return err } + } for _, value := range voices { - msg := tgbotapi.NewVoiceUpload(user, value) - if err := p.Send(bot, msg); err != nil { + f, err := os.Open(value) + if err != nil { + return err + } + defer f.Close() + _, err = bot.SendVoice(background, &tgbotapi.SendVoiceParams{ + ChatID: user, + Voice: &models.InputFileUpload{Filename: value, Data: f}, + DisableNotification: p.Config.DisableNotification, + }) + if err != nil { return err } } for _, value := range videos { - msg := tgbotapi.NewVideoUpload(user, value) - msg.Caption = "Video Message" - if err := p.Send(bot, msg); err != nil { + f, err := os.Open(value) + if err != nil { + return err + } + defer f.Close() + _, err = bot.SendVideo(background, &tgbotapi.SendVideoParams{ + ChatID: user, + Video: &models.InputFileUpload{Filename: value, Data: f}, + DisableNotification: p.Config.DisableNotification, + }) + if err != nil { return err } } @@ -426,8 +472,12 @@ func (p Plugin) Exec() (err error) { continue } - msg := tgbotapi.NewLocation(user, location.Latitude, location.Longitude) - if err := p.Send(bot, msg); err != nil { + _, err = bot.SendLocation(background, &tgbotapi.SendLocationParams{ + ChatID: user, + Latitude: location.Latitude, + Longitude: location.Longitude, + }) + if err != nil { return err } } @@ -439,8 +489,14 @@ func (p Plugin) Exec() (err error) { continue } - msg := tgbotapi.NewVenue(user, location.Title, location.Address, location.Latitude, location.Longitude) - if err := p.Send(bot, msg); err != nil { + _, err = bot.SendVenue(background, &tgbotapi.SendVenueParams{ + ChatID: user, + Title: location.Title, + Address: location.Address, + Latitude: location.Latitude, + Longitude: location.Longitude, + }) + if err != nil { return err } } @@ -449,23 +505,6 @@ func (p Plugin) Exec() (err error) { return nil } -// Send bot message. -func (p Plugin) Send(bot *tgbotapi.BotAPI, msg tgbotapi.Chattable) error { - message, err := bot.Send(msg) - - if p.Config.Debug { - log.Println("=====================") - log.Printf("Response Message: %#v\n", message) - log.Println("=====================") - } - - if err == nil { - return nil - } - - return errors.New(strings.ReplaceAll(err.Error(), p.Config.Token, "")) -} - // Message is plugin default message. func (p Plugin) Message() []string { icon := icons[strings.ToLower(p.Build.Status)] @@ -496,3 +535,22 @@ func (p Plugin) Message() []string { p.Build.Link, )} } + +func (p Plugin) newBot() (*tgbotapi.Bot, error) { + var err error + var proxyURL *url.URL + if proxyURL, err = url.Parse(p.Config.Socks5); err != nil { + return nil, fmt.Errorf("unable to unmarshall socks5 proxy url from string '%s': %v", p.Config.Socks5, err) + } + + var options []tgbotapi.Option + if len(p.Config.Socks5) > 0 { + proxyClient := &http.Client{Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)}} + httpOpts := tgbotapi.WithHTTPClient(time.Minute, proxyClient) + options = append(options, httpOpts) + } + if p.Config.Debug { + options = append(options, tgbotapi.WithDebug()) + } + return tgbotapi.New(p.Config.Token, options...) +} From 9379929603c1f7519bf2240bbcf6bfd4ad4d46f5 Mon Sep 17 00:00:00 2001 From: Nikolay Ushmodin Date: Wed, 22 Oct 2025 15:18:00 +0700 Subject: [PATCH 2/3] MessageThreadID support --- plugin.go | 89 ++++++++++++++++++++++++++++++++------------------ plugin_test.go | 10 +++--- 2 files changed, 63 insertions(+), 36 deletions(-) diff --git a/plugin.go b/plugin.go index c17e64d..8cea427 100644 --- a/plugin.go +++ b/plugin.go @@ -112,6 +112,11 @@ type ( Latitude float64 Longitude float64 } + + Chat struct { + ChatID int64 + MessageThreadID int + } ) var icons = map[string]string{ @@ -224,41 +229,54 @@ func loadTextFromFile(filename string) ([]string, error) { return []string{string(content)}, nil } -func parseTo(to []string, authorEmail string, matchEmail bool) []int64 { - var emails []int64 - var ids []int64 +func parseTo(to []string, authorEmail string, matchEmail bool) []Chat { + var emails []Chat + var chats []Chat attachEmail := true for _, value := range trimElement(to) { - idArray := trimElement(strings.Split(value, ":")) + items := trimElement(strings.Split(value, ":")) + + chatStr := items[0] + var threadStr string + + if strings.Contains(chatStr, "#") { + pos := strings.Index(chatStr, "#") + threadStr = chatStr[pos+1:] + chatStr = chatStr[:pos] + } // check id - id, err := strconv.ParseInt(idArray[0], 10, 64) + id, err := strconv.ParseInt(chatStr, 10, 64) if err != nil { continue } + thread, err := strconv.ParseInt(threadStr, 10, 32) + if err != nil { + thread = 0 + } // check match author email - if len(idArray) > 1 { - if email := idArray[1]; email != authorEmail { + if len(items) > 1 { + if email := items[1]; email != authorEmail { continue } - emails = append(emails, id) + emails = append(emails, Chat{ChatID: id, MessageThreadID: int(thread)}) attachEmail = false continue } - ids = append(ids, id) + chats = append(chats, Chat{ChatID: id, MessageThreadID: int(thread)}) } if matchEmail && !attachEmail { return emails } - ids = append(ids, emails...) + chats = append(chats, emails...) - return ids + return chats } func templateMessage(t string, plugin Plugin) (string, error) { @@ -316,7 +334,7 @@ func (p Plugin) Exec() (err error) { return err } - ids := parseTo(p.Config.To, p.Commit.Email, p.Config.MatchEmail) + chats := parseTo(p.Config.To, p.Commit.Email, p.Config.MatchEmail) photos := globList(trimElement(p.Config.Photo)) documents := globList(trimElement(p.Config.Document)) stickers := globList(trimElement(p.Config.Sticker)) @@ -347,7 +365,7 @@ func (p Plugin) Exec() (err error) { background := context.Background() // send message. - for _, user := range ids { + for _, chat := range chats { for _, value := range message { txt, err := templateMessage(value, p) if err != nil { @@ -356,9 +374,10 @@ func (p Plugin) Exec() (err error) { txt = html.UnescapeString(txt) _, err = bot.SendMessage(background, &tgbotapi.SendMessageParams{ - ChatID: user, - Text: txt, - ParseMode: models.ParseMode(p.Config.Format), + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, + Text: txt, + ParseMode: models.ParseMode(p.Config.Format), LinkPreviewOptions: &models.LinkPreviewOptions{ IsDisabled: &p.Config.DisableWebPagePreview, }, @@ -376,8 +395,9 @@ func (p Plugin) Exec() (err error) { } defer f.Close() _, err = bot.SendPhoto(background, &tgbotapi.SendPhotoParams{ - ChatID: user, - Photo: &models.InputFileUpload{Filename: value, Data: f}, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, + Photo: &models.InputFileUpload{Filename: value, Data: f}, }) if err != nil { return err @@ -391,7 +411,8 @@ func (p Plugin) Exec() (err error) { } defer f.Close() _, err = bot.SendDocument(background, &tgbotapi.SendDocumentParams{ - ChatID: user, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, Document: &models.InputFileUpload{Filename: value, Data: f}, DisableNotification: p.Config.DisableNotification, }) @@ -407,7 +428,8 @@ func (p Plugin) Exec() (err error) { } defer f.Close() _, err = bot.SendSticker(background, &tgbotapi.SendStickerParams{ - ChatID: user, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, Sticker: &models.InputFileUpload{Filename: value, Data: f}, DisableNotification: p.Config.DisableNotification, }) @@ -423,7 +445,8 @@ func (p Plugin) Exec() (err error) { } defer f.Close() _, err = bot.SendAudio(background, &tgbotapi.SendAudioParams{ - ChatID: user, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, Audio: &models.InputFileUpload{Filename: value, Data: f}, DisableNotification: p.Config.DisableNotification, }) @@ -440,7 +463,8 @@ func (p Plugin) Exec() (err error) { } defer f.Close() _, err = bot.SendVoice(background, &tgbotapi.SendVoiceParams{ - ChatID: user, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, Voice: &models.InputFileUpload{Filename: value, Data: f}, DisableNotification: p.Config.DisableNotification, }) @@ -456,7 +480,8 @@ func (p Plugin) Exec() (err error) { } defer f.Close() _, err = bot.SendVideo(background, &tgbotapi.SendVideoParams{ - ChatID: user, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, Video: &models.InputFileUpload{Filename: value, Data: f}, DisableNotification: p.Config.DisableNotification, }) @@ -473,9 +498,10 @@ func (p Plugin) Exec() (err error) { } _, err = bot.SendLocation(background, &tgbotapi.SendLocationParams{ - ChatID: user, - Latitude: location.Latitude, - Longitude: location.Longitude, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, + Latitude: location.Latitude, + Longitude: location.Longitude, }) if err != nil { return err @@ -490,11 +516,12 @@ func (p Plugin) Exec() (err error) { } _, err = bot.SendVenue(background, &tgbotapi.SendVenueParams{ - ChatID: user, - Title: location.Title, - Address: location.Address, - Latitude: location.Latitude, - Longitude: location.Longitude, + ChatID: chat.ChatID, + MessageThreadID: chat.MessageThreadID, + Title: location.Title, + Address: location.Address, + Latitude: location.Latitude, + Longitude: location.Longitude, }) if err != nil { return err diff --git a/plugin_test.go b/plugin_test.go index a2672f4..b6c0842 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -254,19 +254,19 @@ func TestEscapeMarkdownOne(t *testing.T) { } func TestParseTo(t *testing.T) { - input := []string{"0", "1:1@gmail.com", "2:2@gmail.com", "3:3@gmail.com", "4", "5"} + input := []string{"0", "1:1@gmail.com", "2:2@gmail.com", "3:3@gmail.com", "4", "5#7"} ids := parseTo(input, "1@gmail.com", false) - assert.Equal(t, []int64{0, 4, 5, 1}, ids) + assert.Equal(t, []Chat{Chat{0, 0}, Chat{4, 0}, Chat{5, 7}, Chat{1, 0}}, ids) ids = parseTo(input, "1@gmail.com", true) - assert.Equal(t, []int64{1}, ids) + assert.Equal(t, []Chat{Chat{1, 0}}, ids) ids = parseTo(input, "a@gmail.com", false) - assert.Equal(t, []int64{0, 4, 5}, ids) + assert.Equal(t, []Chat{Chat{0, 0}, Chat{4, 0}, Chat{5, 7}}, ids) ids = parseTo(input, "a@gmail.com", true) - assert.Equal(t, []int64{0, 4, 5}, ids) + assert.Equal(t, []Chat{Chat{0, 0}, Chat{4, 0}, Chat{5, 7}}, ids) // test empty ids ids = parseTo([]string{"", " ", " "}, "a@gmail.com", true) From 2e0d5076042838e22ebb5694e11d25bbe5fb4164 Mon Sep 17 00:00:00 2001 From: Nikolay Ushmodin Date: Mon, 2 Mar 2026 17:46:35 +0700 Subject: [PATCH 3/3] skipGetMe param --- docker/Dockerfile | 6 +++--- main.go | 6 ++++++ plugin.go | 4 ++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 611cc1c..3898d2f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,12 +3,12 @@ FROM alpine:3.17 ARG TARGETOS ARG TARGETARCH -LABEL maintainer="Bo-Yi Wu " \ +LABEL maintainer="Nik Ushmodin " \ org.label-schema.name="Telegram Plugin" \ - org.label-schema.vendor="Bo-Yi Wu" \ + org.label-schema.vendor="Nik Ushmodin" \ org.label-schema.schema-version="1.0" -LABEL org.opencontainers.image.source=https://github.com/appleboy/drone-telegram +LABEL org.opencontainers.image.source=https://github.com/ushmodin/drone-telegram LABEL org.opencontainers.image.description="plugin for sending telegram notifications" LABEL org.opencontainers.image.licenses=MIT diff --git a/main.go b/main.go index 47f5a41..aefa6c8 100644 --- a/main.go +++ b/main.go @@ -263,6 +263,11 @@ func main() { Usage: "Socks5 proxy URL", EnvVar: "PLUGIN_SOCKS5,SOCKS5,INPUT_SOCKS5", }, + cli.BoolFlag{ + Name: "skipgetme", + Usage: "Boolean value, indicates to skip getMe request", + EnvVar: "PLUGIN_SKIP_GETME,SKIP_GETME", + }, } if err := app.Run(os.Args); err != nil { @@ -324,6 +329,7 @@ func run(c *cli.Context) error { Venue: c.StringSlice("venue"), Format: c.String("format"), GitHub: c.Bool("github"), + SkipGetMe: c.Bool("skipgetme"), Socks5: c.String("socks5"), DisableWebPagePreview: c.Bool("disable.webpage.preview"), diff --git a/plugin.go b/plugin.go index 8cea427..973a217 100644 --- a/plugin.go +++ b/plugin.go @@ -89,6 +89,7 @@ type ( Venue []string Format string GitHub bool + SkipGetMe bool Socks5 string DisableWebPagePreview bool @@ -579,5 +580,8 @@ func (p Plugin) newBot() (*tgbotapi.Bot, error) { if p.Config.Debug { options = append(options, tgbotapi.WithDebug()) } + if p.Config.SkipGetMe { + options = append(options, tgbotapi.WithSkipGetMe()) + } return tgbotapi.New(p.Config.Token, options...) }