File: telegram_generator.go

package info (click to toggle)
golang-github-nicholas-fedor-shoutrrr 0.8.17-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,332 kB
  • sloc: sh: 61; makefile: 5
file content (215 lines) | stat: -rw-r--r-- 5,187 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package telegram

import (
	"errors"
	"fmt"
	"io"
	"os"
	"os/signal"
	"slices"
	"strconv"
	"syscall"

	"github.com/nicholas-fedor/shoutrrr/pkg/format"
	"github.com/nicholas-fedor/shoutrrr/pkg/types"
	"github.com/nicholas-fedor/shoutrrr/pkg/util/generator"
)

// UpdatesLimit defines the number of updates to retrieve per API call.
const (
	UpdatesLimit   = 10  // Number of updates to retrieve per call
	UpdatesTimeout = 120 // Timeout in seconds for long polling
)

// ErrNoChatsSelected indicates that no chats were selected during generation.
var (
	ErrNoChatsSelected = errors.New("no chats were selected")
)

// Generator facilitates Telegram-specific URL generation via user interaction.
type Generator struct {
	userDialog *generator.UserDialog
	client     *Client
	chats      []string
	chatNames  []string
	chatTypes  []string
	done       bool
	botName    string
	Reader     io.Reader
	Writer     io.Writer
}

// Generate creates a Telegram Shoutrrr configuration from user dialog input.
func (g *Generator) Generate(
	_ types.Service,
	props map[string]string,
	_ []string,
) (types.ServiceConfig, error) {
	var config Config

	if g.Reader == nil {
		g.Reader = os.Stdin
	}

	if g.Writer == nil {
		g.Writer = os.Stdout
	}

	g.userDialog = generator.NewUserDialog(g.Reader, g.Writer, props)
	userDialog := g.userDialog

	userDialog.Writelnf(
		"To start we need your bot token. If you haven't created a bot yet, you can use this link:",
	)
	userDialog.Writelnf("  %v", format.ColorizeLink("https://t.me/botfather?start"))
	userDialog.Writelnf("")

	token := userDialog.QueryString(
		"Enter your bot token:",
		generator.ValidateFormat(IsTokenValid),
		"token",
	)

	userDialog.Writelnf("Fetching bot info...")

	g.client = &Client{token: token}

	botInfo, err := g.client.GetBotInfo()
	if err != nil {
		return &Config{}, err
	}

	g.botName = botInfo.Username

	userDialog.Writelnf("")
	userDialog.Writelnf(
		"Okay! %v will listen for any messages in PMs and group chats it is invited to.",
		format.ColorizeString("@", g.botName),
	)

	g.done = false
	lastUpdate := 0

	signals := make(chan os.Signal, 1)

	// Subscribe to system signals
	signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

	for !g.done {
		userDialog.Writelnf("Waiting for messages to arrive...")

		updates, err := g.client.GetUpdates(lastUpdate, UpdatesLimit, UpdatesTimeout, nil)
		if err != nil {
			panic(err)
		}

		// If no updates were retrieved, prompt user to continue
		promptDone := len(updates) == 0

		for _, update := range updates {
			lastUpdate = update.UpdateID + 1

			switch {
			case update.Message != nil || update.ChannelPost != nil:
				message := update.Message
				if update.ChannelPost != nil {
					message = update.ChannelPost
				}

				chat := message.Chat

				source := message.Chat.Name()
				if message.From != nil {
					source = "@" + message.From.Username
				}

				userDialog.Writelnf("Got Message '%v' from %v in %v chat %v",
					format.ColorizeString(message.Text),
					format.ColorizeProp(source),
					format.ColorizeEnum(chat.Type),
					format.ColorizeNumber(chat.ID))
				userDialog.Writelnf(g.addChat(chat))
				// Another chat was added, prompt user to continue
				promptDone = true

			case update.ChatMemberUpdate != nil:
				cmu := update.ChatMemberUpdate
				oldStatus := cmu.OldChatMember.Status
				newStatus := cmu.NewChatMember.Status
				userDialog.Writelnf(
					"Got a bot chat member update for %v, status was changed from %v to %v",
					format.ColorizeProp(cmu.Chat.Name()),
					format.ColorizeEnum(oldStatus),
					format.ColorizeEnum(newStatus),
				)

			default:
				userDialog.Writelnf("Got unknown Update. Ignored!")
			}
		}

		if promptDone {
			userDialog.Writelnf("")

			g.done = !userDialog.QueryBool(
				fmt.Sprintf("Got %v chat ID(s) so far. Want to add some more?",
					format.ColorizeNumber(len(g.chats))),
				"",
			)
		}
	}

	userDialog.Writelnf("")
	userDialog.Writelnf("Cleaning up the bot session...")

	// Notify API that we got the updates
	if _, err = g.client.GetUpdates(lastUpdate, 0, 0, nil); err != nil {
		g.userDialog.Writelnf(
			"Failed to mark last updates as received: %v",
			format.ColorizeError(err),
		)
	}

	if len(g.chats) < 1 {
		return nil, ErrNoChatsSelected
	}

	userDialog.Writelnf("Selected chats:")

	for i, id := range g.chats {
		name := g.chatNames[i]
		chatType := g.chatTypes[i]
		userDialog.Writelnf(
			"  %v (%v) %v",
			format.ColorizeNumber(id),
			format.ColorizeEnum(chatType),
			format.ColorizeString(name),
		)
	}

	userDialog.Writelnf("")

	config = Config{
		Notification: true,
		Token:        token,
		Chats:        g.chats,
	}

	return &config, nil
}

// addChat adds a chat to the generator's list if it’s not already present.
func (g *Generator) addChat(chat *Chat) string {
	chatID := strconv.FormatInt(chat.ID, 10)
	name := chat.Name()

	if slices.Contains(g.chats, chatID) {
		return fmt.Sprintf("chat %v is already selected!", format.ColorizeString(name))
	}

	g.chats = append(g.chats, chatID)
	g.chatNames = append(g.chatNames, name)
	g.chatTypes = append(g.chatTypes, chat.Type)

	return fmt.Sprintf("Added new chat %v!", format.ColorizeString(name))
}