Skip to content

Commit a608b28

Browse files
authored
feat: Telegram authorization capability (#599)
* feat(telegram): add authorization capability Signed-off-by: cryptodj413 <shinjirohara2@gmail.com> * fix(telegram): address AI review Signed-off-by: cryptodj413 <shinjirohara2@gmail.com> * fix(telegram): guard eventChan and errorChan on double-start Signed-off-by: cryptodj413 <shinjirohara2@gmail.com> --------- Signed-off-by: cryptodj413 <shinjirohara2@gmail.com>
1 parent cf70e05 commit a608b28

File tree

8 files changed

+1132
-1
lines changed

8 files changed

+1132
-1
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
github.com/btcsuite/btcd/btcutil v1.1.6
1313
github.com/gen2brain/beeep v0.11.2
1414
github.com/gin-gonic/gin v1.11.0
15+
github.com/go-telegram/bot v1.18.0
1516
github.com/inconshreveable/mousetrap v1.1.0
1617
github.com/kelseyhightower/envconfig v1.4.0
1718
github.com/spf13/cobra v1.10.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
118118
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
119119
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
120120
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
121+
github.com/go-telegram/bot v1.18.0 h1:yQzv437DY42SYTPBY48RinAvwbmf1ox5QICskIYWCD8=
122+
github.com/go-telegram/bot v1.18.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
121123
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
122124
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
123125
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=

internal/logging/logging.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,14 @@ import (
2222
"github.com/blinklabs-io/adder/internal/config"
2323
)
2424

25-
var globalLogger *slog.Logger
25+
// defaultLogger returns a non-nil logger so globalLogger is never nil at declaration (satisfies nilaway).
26+
func defaultLogger() *slog.Logger {
27+
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
28+
Level: slog.LevelInfo,
29+
})).With("component", "main")
30+
}
31+
32+
var globalLogger = defaultLogger()
2633

2734
func Configure() {
2835
cfg := config.GetConfig()

output/output.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ import (
1919
_ "github.com/blinklabs-io/adder/output/log"
2020
_ "github.com/blinklabs-io/adder/output/notify"
2121
_ "github.com/blinklabs-io/adder/output/push"
22+
_ "github.com/blinklabs-io/adder/output/telegram"
2223
_ "github.com/blinklabs-io/adder/output/webhook"
2324
)

output/telegram/options.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package telegram
16+
17+
import (
18+
"time"
19+
20+
"github.com/blinklabs-io/adder/plugin"
21+
"github.com/go-telegram/bot/models"
22+
)
23+
24+
// TelegramOptionFunc is a function type for configuring TelegramOutput
25+
type TelegramOptionFunc func(*TelegramOutput)
26+
27+
// WithLogger specifies the logger object to use for logging messages
28+
func WithLogger(logger plugin.Logger) TelegramOptionFunc {
29+
return func(t *TelegramOutput) {
30+
t.logger = logger
31+
}
32+
}
33+
34+
// WithBotToken specifies the Telegram Bot API token
35+
// This token is obtained from @BotFather on Telegram
36+
func WithBotToken(token string) TelegramOptionFunc {
37+
return func(t *TelegramOutput) {
38+
t.botToken = token
39+
}
40+
}
41+
42+
// WithChatID specifies the chat ID to send messages to
43+
// This can be a user ID, group ID, or channel ID
44+
// For groups/channels, the ID is typically negative
45+
func WithChatID(chatID int64) TelegramOptionFunc {
46+
return func(t *TelegramOutput) {
47+
t.chatID = chatID
48+
}
49+
}
50+
51+
// WithParseMode specifies the message parse mode
52+
// Options: HTML, Markdown (legacy), MarkdownV2 (default markdown)
53+
func WithParseMode(mode string) TelegramOptionFunc {
54+
return func(t *TelegramOutput) {
55+
switch mode {
56+
case "HTML":
57+
t.parseMode = models.ParseModeHTML
58+
case "Markdown":
59+
t.parseMode = models.ParseModeMarkdownV1
60+
case "MarkdownV2":
61+
t.parseMode = models.ParseModeMarkdown
62+
default:
63+
t.parseMode = models.ParseModeHTML
64+
}
65+
}
66+
}
67+
68+
// WithDisableLinkPreview disables link preview in messages
69+
func WithDisableLinkPreview(disable bool) TelegramOptionFunc {
70+
return func(t *TelegramOutput) {
71+
t.disablePreview = disable
72+
}
73+
}
74+
75+
// WithRetryConfig specifies the retry configuration for message delivery
76+
func WithRetryConfig(
77+
maxRetries int,
78+
initialBackoff, maxBackoff time.Duration,
79+
) TelegramOptionFunc {
80+
return func(t *TelegramOutput) {
81+
if maxRetries >= 0 {
82+
t.maxRetries = maxRetries
83+
}
84+
if initialBackoff > 0 {
85+
t.initialBackoff = initialBackoff
86+
}
87+
if maxBackoff > 0 {
88+
t.maxBackoff = maxBackoff
89+
}
90+
}
91+
}

output/telegram/plugin.go

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package telegram
16+
17+
import (
18+
"strconv"
19+
20+
"github.com/blinklabs-io/adder/internal/logging"
21+
"github.com/blinklabs-io/adder/plugin"
22+
)
23+
24+
var cmdlineOptions struct {
25+
botToken string
26+
chatID string // String to support int64 values (groups have negative IDs)
27+
parseMode string
28+
disablePreview bool
29+
}
30+
31+
func init() {
32+
plugin.Register(
33+
plugin.PluginEntry{
34+
Type: plugin.PluginTypeOutput,
35+
Name: "telegram",
36+
Description: "send events to a Telegram chat or channel",
37+
NewFromOptionsFunc: NewFromCmdlineOptions,
38+
Options: []plugin.PluginOption{
39+
{
40+
Name: "bot-token",
41+
Type: plugin.PluginOptionTypeString,
42+
Description: "Telegram Bot API token (from @BotFather)",
43+
DefaultValue: "",
44+
Dest: &(cmdlineOptions.botToken),
45+
},
46+
{
47+
Name: "chat-id",
48+
Type: plugin.PluginOptionTypeString,
49+
Description: "Telegram chat ID to send messages to (user, group, or channel)",
50+
DefaultValue: "",
51+
Dest: &(cmdlineOptions.chatID),
52+
},
53+
{
54+
Name: "parse-mode",
55+
Type: plugin.PluginOptionTypeString,
56+
Description: "message parse mode (HTML, Markdown, MarkdownV2)",
57+
DefaultValue: "HTML",
58+
Dest: &(cmdlineOptions.parseMode),
59+
},
60+
{
61+
Name: "disable-preview",
62+
Type: plugin.PluginOptionTypeBool,
63+
Description: "disable link preview in messages",
64+
DefaultValue: false,
65+
Dest: &(cmdlineOptions.disablePreview),
66+
},
67+
},
68+
},
69+
)
70+
}
71+
72+
func NewFromCmdlineOptions() plugin.Plugin {
73+
logger := logging.GetLogger()
74+
75+
if cmdlineOptions.chatID == "" {
76+
logger.Error("chat ID is required for Telegram output (--output-telegram-chat-id or OUTPUT_TELEGRAM_CHAT_ID)")
77+
return nil
78+
}
79+
80+
// Parse chat ID from string to int64
81+
chatID, err := strconv.ParseInt(cmdlineOptions.chatID, 10, 64)
82+
if err != nil {
83+
logger.Error("invalid chat ID", "error", err, "chat_id", cmdlineOptions.chatID)
84+
return nil
85+
}
86+
87+
p, err := New(
88+
WithLogger(
89+
logger.With("plugin", "output.telegram"),
90+
),
91+
WithBotToken(cmdlineOptions.botToken),
92+
WithChatID(chatID),
93+
WithParseMode(cmdlineOptions.parseMode),
94+
WithDisableLinkPreview(cmdlineOptions.disablePreview),
95+
)
96+
if err != nil {
97+
logger.Error("failed to create Telegram output", "error", err)
98+
return nil
99+
}
100+
return p
101+
}

0 commit comments

Comments
 (0)