Rework architecture, fix env parsing

This commit is contained in:
2024-07-29 00:10:35 +03:00
parent e2af48ccd7
commit 8fa95db24b
7 changed files with 272 additions and 84 deletions
+19
View File
@@ -0,0 +1,19 @@
package main
import (
"github.com/kelseyhightower/envconfig"
"github.com/pkg/errors"
)
func parseEnv() (*config, error) {
c := new(config)
if err := envconfig.Process("", c); err != nil {
return nil, errors.Wrap(err, "failed to parse env")
}
return c, nil
}
type config struct {
TelegramBotToken string `envconfig:"telegram_bot_token"`
OwnerChatID int64 `envconfig:"owner_chat_id"`
}
+18 -37
View File
@@ -2,54 +2,35 @@ package main
import ( import (
"log" "log"
"os"
"strconv"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/nightnoryu/anon3anon/pkg/app" "github.com/nightnoryu/anon3anon/pkg/app"
"github.com/nightnoryu/anon3anon/pkg/infrastructure"
) )
const (
telegramBotTokenEnvKey = "TELEGRAM_BOT_TOKEN"
ownerChatIDEnvKey = "OWNER_CHAT_ID"
)
type config struct {
Token string
OwnerChatID int64
}
func getConfig() (config, error) {
token := os.Getenv(telegramBotTokenEnvKey)
ownerChatID, err := strconv.ParseInt(os.Getenv(ownerChatIDEnvKey), 10, 64)
if err != nil {
return config{}, err
}
return config{
Token: token,
OwnerChatID: ownerChatID,
}, nil
}
func main() { func main() {
config, err := getConfig() conf, err := parseEnv()
if err != nil {
log.Fatal(err)
}
log.Println(conf)
bot, err := tgbotapi.NewBotAPI(conf.TelegramBotToken)
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
bot, err := tgbotapi.NewBotAPI(config.Token)
if err != nil {
log.Panic(err)
}
//bot.Debug = true
log.Printf("Authorized on account %s", bot.Self.UserName) log.Printf("Authorized on account %s", bot.Self.UserName)
service := app.NewAnonymousQuestionsService(bot, config.OwnerChatID) botAPI := infrastructure.NewBotAPI(bot, conf.OwnerChatID)
errorsChan := make(chan error)
service := app.NewAnonymousQuestionsService(botAPI, errorsChan)
go func() {
for err := range errorsChan {
log.Println(err)
}
}()
if err := service.ListenForMessages(); err != nil { if err = service.ServeMessages(); err != nil {
log.Panic(err) log.Fatal(err)
} }
} }
+3 -2
View File
@@ -3,6 +3,7 @@ module github.com/nightnoryu/anon3anon
go 1.22.5 go 1.22.5
require ( require (
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/pkg/errors v0.9.1 // indirect github.com/kelseyhightower/envconfig v1.4.0
github.com/pkg/errors v0.9.1
) )
+2
View File
@@ -1,4 +1,6 @@
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+51 -45
View File
@@ -1,66 +1,72 @@
package app package app
import ( func NewAnonymousQuestionsService(api BotAPI, errorsChan chan error) AnonymousMessagesService {
"log"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
func NewAnonymousQuestionsService(bot *tgbotapi.BotAPI, ownerChatID int64) AnonymousMessagesService {
return &anonymousMessagesService{ return &anonymousMessagesService{
bot: bot, api: api,
ownerChatID: ownerChatID, errorsChan: errorsChan,
} }
} }
const (
messageSentReply = "*Сообщение отправлено!*"
newMessageNotification = "*Новое анонимное сообщение!*"
)
type AnonymousMessagesService interface { type AnonymousMessagesService interface {
ListenForMessages() error ServeMessages() error
} }
type anonymousMessagesService struct { type anonymousMessagesService struct {
bot *tgbotapi.BotAPI api BotAPI
ownerChatID int64 errorsChan chan error
} }
func (s *anonymousMessagesService) ListenForMessages() error { func (s *anonymousMessagesService) ServeMessages() error {
u := tgbotapi.NewUpdate(0) return s.api.HandleUpdates(func(update MessageUpdate) {
u.Timeout = 60 if update.Command != nil {
err := s.handleCommand(update)
updates := s.bot.GetUpdatesChan(u) if err != nil {
for update := range updates { s.errorsChan <- err
if update.Message == nil { }
continue return
} }
log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text) err := s.handleMessage(update.Message)
if err != nil {
if update.Message.Text == "/start" { s.errorsChan <- err
reply := tgbotapi.NewMessage(update.Message.Chat.ID, "Жду твоих вопросов!")
s.bot.Send(reply)
continue
} }
if len(update.Message.Photo) > 0 { err = s.pingClient(update.FromChatID)
var photos []interface{} if err != nil {
photo := tgbotapi.NewInputMediaPhoto(tgbotapi.FileID(update.Message.Photo[0].FileID)) s.errorsChan <- err
photo.Caption = "*Новое анонимное сообщение!*"
photo.ParseMode = tgbotapi.ModeMarkdown
photos = append(photos, photo)
mediaMsg := tgbotapi.NewMediaGroup(s.ownerChatID, photos)
s.bot.Send(mediaMsg)
} else {
msg := tgbotapi.NewMessage(s.ownerChatID, "*Новое анонимное сообщение!*\n\n"+update.Message.Text)
msg.ParseMode = tgbotapi.ModeMarkdown
s.bot.Send(msg)
} }
})
}
reply := tgbotapi.NewMessage(update.Message.Chat.ID, "Сообщение отправлено!") func (s *anonymousMessagesService) handleCommand(update MessageUpdate) error {
if update.Command == nil {
s.bot.Send(reply) return nil
} }
return nil var msgText string
switch *update.Command {
case StartCommand:
msgText = "Жду твоих вопросов!"
case UnknownCommand:
msgText = "Неизвестная команда!"
}
return s.api.SendMessage(update.FromChatID, Message{Text: msgText})
}
func (s *anonymousMessagesService) pingClient(chatID int64) error {
return s.api.SendMessage(chatID, Message{Text: messageSentReply})
}
func (s *anonymousMessagesService) handleMessage(message Message) error {
msgText := newMessageNotification + "\n\n" + message.Text
return s.api.SendMessageToOwner(Message{
Text: msgText,
Image: message.Image,
})
} }
+32
View File
@@ -0,0 +1,32 @@
package app
type BotAPI interface {
HandleUpdates(handler MessageUpdateHandler) error
SendMessage(chatID int64, message Message) error
SendMessageToOwner(message Message) error
}
type MessageUpdateHandler func(MessageUpdate)
type MessageUpdate struct {
Message
UpdateID int
FromChatID int64
Command *Command
}
type Message struct {
Text string
Image *Image
}
type Command int
const (
UnknownCommand Command = iota
StartCommand
)
type Image struct {
FileID string
}
+147
View File
@@ -0,0 +1,147 @@
package infrastructure
import (
"log"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/pkg/errors"
"github.com/nightnoryu/anon3anon/pkg/app"
)
const (
updateTimeoutInSeconds = 60
startCommand = "/start"
messageParseMode = tgbotapi.ModeMarkdown
)
type fileInfo struct {
FileID string
Size int
}
func NewBotAPI(bot *tgbotapi.BotAPI, ownerChatID int64) app.BotAPI {
return &botAPI{
bot: bot,
ownerChatID: ownerChatID,
}
}
type botAPI struct {
bot *tgbotapi.BotAPI
ownerChatID int64
}
func (api *botAPI) HandleUpdates(handler app.MessageUpdateHandler) error {
u := tgbotapi.NewUpdate(0)
u.Timeout = updateTimeoutInSeconds
updates := api.bot.GetUpdatesChan(u)
for update := range updates {
if update.Message == nil {
continue
}
log.Printf("%+v\n", update.Message)
messageUpdate := app.MessageUpdate{
Message: api.hydrateMessage(update.Message),
UpdateID: update.UpdateID,
FromChatID: update.FromChat().ID,
Command: api.hydrateCommand(update.Message),
}
handler(messageUpdate)
}
return nil
}
func (api *botAPI) SendMessage(chatID int64, message app.Message) error {
if message.Image != nil {
return api.sendPhotoMessage(chatID, message)
}
return api.sendTextMessage(chatID, message)
}
func (api *botAPI) SendMessageToOwner(message app.Message) error {
return api.SendMessage(api.ownerChatID, message)
}
func (api *botAPI) hydrateMessage(msg *tgbotapi.Message) app.Message {
text := msg.Text
if len(text) == 0 {
text = msg.Caption
}
return app.Message{
Text: text,
Image: api.hydrateImage(msg.Photo),
}
}
func (api *botAPI) sendTextMessage(chatID int64, message app.Message) error {
msg := tgbotapi.NewMessage(
chatID,
message.Text,
)
msg.ParseMode = messageParseMode
_, err := api.bot.Send(msg)
return errors.WithStack(err)
}
func (api *botAPI) sendPhotoMessage(chatID int64, message app.Message) error {
photos := api.preparePhotos(message)
mediaMsg := tgbotapi.NewMediaGroup(chatID, photos)
_, err := api.bot.Send(mediaMsg)
return errors.WithStack(err)
}
func (api *botAPI) hydrateCommand(msg *tgbotapi.Message) *app.Command {
if !msg.IsCommand() {
return nil
}
var cmd app.Command
switch msg.Command() {
case startCommand:
cmd = app.StartCommand
default:
cmd = app.UnknownCommand
}
return &cmd
}
func (api *botAPI) hydrateImage(photos []tgbotapi.PhotoSize) *app.Image {
if len(photos) == 0 {
return nil
}
var originalFileID string
var originalFileSize int
for _, photo := range photos {
if photo.FileSize > originalFileSize {
originalFileID = photo.FileID
originalFileSize = photo.FileSize
}
}
return &app.Image{
FileID: originalFileID,
}
}
func (api *botAPI) preparePhotos(message app.Message) []interface{} {
photo := tgbotapi.NewInputMediaPhoto(tgbotapi.FileID(message.Image.FileID))
photo.Caption = message.Text
photo.ParseMode = messageParseMode
var photos []interface{}
photos = append(photos, photo)
return photos
}