215 lines
5.1 KiB
Go
215 lines
5.1 KiB
Go
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))
|
||
}
|