From bb8f0391cc93e000c088ec21dedbbd945cde93f6 Mon Sep 17 00:00:00 2001 From: Damon To Date: Fri, 21 Jun 2024 15:30:58 +0800 Subject: [PATCH] feat: improve code and support for proxy from environment --- config/config.go | 5 +- internal/app/app.go | 6 +- internal/app/handler/chip.go | 2 +- internal/app/handler/download.go | 22 +++---- internal/app/handler/handler.go | 11 ++-- internal/app/handler/profile.go | 44 +++++++++----- internal/app/handler/send.go | 20 +++--- internal/app/handler/ussd.go | 20 +++--- internal/app/middleware/modem.go | 1 - internal/app/middleware/state.go | 15 +++++ internal/app/routes/router.go | 9 ++- internal/app/routes/routes.go | 2 + internal/pkg/conversation/conversation.go | 74 ----------------------- internal/pkg/modem/manager.go | 2 +- internal/pkg/state/state.go | 71 ++++++++++++++++++++++ main.go | 11 +++- 16 files changed, 178 insertions(+), 137 deletions(-) create mode 100644 internal/app/middleware/state.go delete mode 100644 internal/pkg/conversation/conversation.go create mode 100644 internal/pkg/state/state.go diff --git a/config/config.go b/config/config.go index 2241d12..cf98662 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,8 @@ package config -import "errors" +import ( + "errors" +) type Config struct { BotToken string @@ -8,6 +10,7 @@ type Config struct { Dir string Version string DontDownload bool + Proxy string Verbose bool } diff --git a/internal/app/app.go b/internal/app/app.go index a19fbcf..08906ba 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -4,7 +4,7 @@ import ( "log/slog" "github.com/damonto/telegram-sms/internal/app/routes" - "github.com/damonto/telegram-sms/internal/pkg/conversation" + "github.com/damonto/telegram-sms/internal/pkg/state" "gopkg.in/telebot.v3" "gopkg.in/telebot.v3/middleware" ) @@ -27,9 +27,7 @@ func (a *app) setup() error { a.bot.Use(middleware.Recover()) a.bot.Use(middleware.AutoRespond()) - conversation.NewConversation(a.bot) - - if err := routes.NewRouter(a.bot).Setup(); err != nil { + if err := routes.NewRouter(a.bot, state.NewState(a.bot)).Setup(); err != nil { slog.Error("failed to setup router", "error", err) return err } diff --git a/internal/app/handler/chip.go b/internal/app/handler/chip.go index 2b2e6b8..14088b2 100644 --- a/internal/app/handler/chip.go +++ b/internal/app/handler/chip.go @@ -17,7 +17,7 @@ type ChipHandler struct { func HandleChipCommand(c telebot.Context) error { h := &ChipHandler{} - h.setModem(c) + h.init(c) return h.handle(c) } diff --git a/internal/app/handler/download.go b/internal/app/handler/download.go index 2dbe551..c60003d 100644 --- a/internal/app/handler/download.go +++ b/internal/app/handler/download.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/damonto/telegram-sms/internal/pkg/conversation" "github.com/damonto/telegram-sms/internal/pkg/lpac" + "github.com/damonto/telegram-sms/internal/pkg/state" "github.com/damonto/telegram-sms/internal/pkg/util" "gopkg.in/telebot.v3" ) @@ -15,7 +15,7 @@ import ( type DownloadHandler struct { handler activationCode *lpac.ActivationCode - conversation conversation.Conversation + state state.State } const ( @@ -25,9 +25,9 @@ const ( func HandleDownloadCommand(c telebot.Context) error { h := &DownloadHandler{} - h.setModem(c) - h.conversation = conversation.New(c) - h.conversation.Flow(map[string]telebot.HandlerFunc{ + h.init(c) + h.state = h.stateManager.New(c) + h.state.Stages(map[string]telebot.HandlerFunc{ DownloadAskActivationCode: h.handleActivationCode, DownloadAskConfirmationCode: h.handleConfirmationCode, }) @@ -35,14 +35,14 @@ func HandleDownloadCommand(c telebot.Context) error { } func (h *DownloadHandler) handle(c telebot.Context) error { - h.conversation.Next(DownloadAskActivationCode) + h.state.Next(DownloadAskActivationCode) return c.Send("Please send me the activation code") } func (h *DownloadHandler) handleActivationCode(c telebot.Context) error { activationCode := c.Text() if activationCode == "" || !strings.HasPrefix(activationCode, "LPA:1$") { - h.conversation.Next(DownloadAskActivationCode) + h.state.Next(DownloadAskActivationCode) return c.Send("Invalid activation code.") } @@ -52,23 +52,23 @@ func (h *DownloadHandler) handleActivationCode(c telebot.Context) error { MatchingId: parts[2], } if len(parts) == 5 && parts[4] == "1" { - h.conversation.Next(DownloadAskConfirmationCode) + h.state.Next(DownloadAskConfirmationCode) return c.Send("Please send me the confirmation code") } - h.conversation.Done() + h.stateManager.Done(c) return h.download(c) } func (h *DownloadHandler) handleConfirmationCode(c telebot.Context) error { confirmationCode := c.Text() if confirmationCode == "" { - h.conversation.Next(DownloadAskConfirmationCode) + h.state.Next(DownloadAskConfirmationCode) return c.Send("Invalid confirmation code") } h.activationCode.ConfirmationCode = confirmationCode - h.conversation.Done() + h.stateManager.Done(c) return h.download(c) } diff --git a/internal/app/handler/handler.go b/internal/app/handler/handler.go index ec16c85..e7d7028 100644 --- a/internal/app/handler/handler.go +++ b/internal/app/handler/handler.go @@ -2,15 +2,16 @@ package handler import ( "github.com/damonto/telegram-sms/internal/pkg/modem" + "github.com/damonto/telegram-sms/internal/pkg/state" "gopkg.in/telebot.v3" ) type handler struct { - modem *modem.Modem + modem *modem.Modem + stateManager *state.StateManager } -func (h *handler) setModem(c telebot.Context) *modem.Modem { - modem := c.Get("modem").(*modem.Modem) - h.modem = modem - return modem +func (h *handler) init(c telebot.Context) { + h.modem = c.Get("modem").(*modem.Modem) + h.stateManager = c.Get("state").(*state.StateManager) } diff --git a/internal/app/handler/profile.go b/internal/app/handler/profile.go index eeebc7a..77da16b 100644 --- a/internal/app/handler/profile.go +++ b/internal/app/handler/profile.go @@ -6,8 +6,8 @@ import ( "log/slog" "time" - "github.com/damonto/telegram-sms/internal/pkg/conversation" "github.com/damonto/telegram-sms/internal/pkg/lpac" + "github.com/damonto/telegram-sms/internal/pkg/state" "github.com/damonto/telegram-sms/internal/pkg/util" "github.com/google/uuid" "gopkg.in/telebot.v3" @@ -15,8 +15,8 @@ import ( type ProfileHandler struct { handler - conversation conversation.Conversation - ICCID string + state state.State + ICCID string } const ( @@ -31,9 +31,9 @@ const ( func HandleProfilesCommand(c telebot.Context) error { h := &ProfileHandler{} - h.setModem(c) - h.conversation = conversation.New(c) - h.conversation.Flow(map[string]telebot.HandlerFunc{ + h.init(c) + h.state = h.stateManager.New(c) + h.state.Stages(map[string]telebot.HandlerFunc{ ProfileStateHandleAction: h.handleAction, ProfileStateActionRename: h.handleActionRename, ProfileStateActionDelete: h.handleActionDelete, @@ -90,7 +90,7 @@ func (h *ProfileHandler) toTextMessage(c telebot.Context, profiles []*lpac.Profi btn := selector.Data(fmt.Sprintf("%s (%s)", name, p.ICCID[len(p.ICCID)-4:]), uuid.New().String(), p.ICCID) c.Bot().Handle(&btn, func(c telebot.Context) error { h.ICCID = c.Data() - h.conversation.Next(ProfileStateHandleAction) + h.state.Next(ProfileStateHandleAction) return h.handleAskAction(c) }) buttons = append(buttons, btn) @@ -104,10 +104,10 @@ func (h *ProfileHandler) handleAction(c telebot.Context) error { case ProfileActionEnable: return h.handleActionEnable(c) case ProfileActionRename: - h.conversation.Next(ProfileStateActionRename) + h.state.Next(ProfileStateActionRename) return c.Send("OK. Send me the new name.") case ProfileActionDelete: - h.conversation.Next(ProfileStateActionDelete) + h.state.Next(ProfileStateActionDelete) return c.Send("Are you sure you want to delete this profile?", &telebot.ReplyMarkup{ OneTimeKeyboard: true, ResizeKeyboard: true, @@ -156,13 +156,29 @@ func (h *ProfileHandler) handleAskAction(c telebot.Context) error { template := ` You've selected the profile: -ICCID: %s +%s *%s* +%s What do you want to do with this profile? ` - return c.Send(util.EscapeText(fmt.Sprintf(template, profile.ICCID)), &telebot.ReplyMarkup{ - OneTimeKeyboard: true, - ResizeKeyboard: true, - ReplyKeyboard: [][]telebot.ReplyButton{buttons}, + name := fmt.Sprintf("[%s] ", profile.ProviderName) + if profile.Nickname != "" { + name += profile.Nickname + } else { + name += profile.ProfileName + } + var emoji string + if profile.State == lpac.ProfileStateEnabled { + emoji = "✅" + } else { + emoji = "🅾️" + } + return c.Send(util.EscapeText(fmt.Sprintf(template, emoji, name, fmt.Sprintf("`%s`", profile.ICCID))), &telebot.SendOptions{ + ParseMode: telebot.ModeMarkdownV2, + ReplyMarkup: &telebot.ReplyMarkup{ + OneTimeKeyboard: true, + ResizeKeyboard: true, + ReplyKeyboard: [][]telebot.ReplyButton{buttons}, + }, }) } diff --git a/internal/app/handler/send.go b/internal/app/handler/send.go index 93ba10a..38000b9 100644 --- a/internal/app/handler/send.go +++ b/internal/app/handler/send.go @@ -3,14 +3,14 @@ package handler import ( "fmt" - "github.com/damonto/telegram-sms/internal/pkg/conversation" + "github.com/damonto/telegram-sms/internal/pkg/state" "gopkg.in/telebot.v3" ) type SendHandler struct { handler - phoneNumber string - conversation conversation.Conversation + phoneNumber string + state state.State } const ( @@ -20,9 +20,9 @@ const ( func HandleSendCommand(c telebot.Context) error { h := &SendHandler{} - h.setModem(c) - h.conversation = conversation.New(c) - h.conversation.Flow(map[string]telebot.HandlerFunc{ + h.init(c) + h.state = h.stateManager.New(c) + h.state.Stages(map[string]telebot.HandlerFunc{ SendAskPhoneNumber: h.handlePhoneNumber, SendAskMessage: h.handleMessage, }) @@ -30,7 +30,7 @@ func HandleSendCommand(c telebot.Context) error { } func (h *SendHandler) handle(c telebot.Context) error { - h.conversation.Next(SendAskPhoneNumber) + h.state.Next(SendAskPhoneNumber) return c.Send("Please send me the phone number you want to send the message to") } @@ -41,7 +41,7 @@ func (h *SendHandler) handlePhoneNumber(c telebot.Context) error { } } - h.conversation.Next(SendAskMessage) + h.state.Next(SendAskMessage) h.phoneNumber = c.Text() return c.Send("Please send me the message you want to send") } @@ -51,10 +51,10 @@ func (h *SendHandler) handleMessage(c telebot.Context) error { c.Send(fmt.Sprintf("Failed to send SMS to *%s*", h.phoneNumber), &telebot.SendOptions{ ParseMode: telebot.ModeMarkdownV2, }) - h.conversation.Done() + h.stateManager.Done(c) return err } - h.conversation.Done() + h.stateManager.Done(c) return c.Send(fmt.Sprintf("Your SMS has been sent to *%s*", h.phoneNumber), &telebot.SendOptions{ ParseMode: telebot.ModeMarkdownV2, }) diff --git a/internal/app/handler/ussd.go b/internal/app/handler/ussd.go index 35cf928..ffca716 100644 --- a/internal/app/handler/ussd.go +++ b/internal/app/handler/ussd.go @@ -3,13 +3,13 @@ package handler import ( "fmt" - "github.com/damonto/telegram-sms/internal/pkg/conversation" + "github.com/damonto/telegram-sms/internal/pkg/state" "gopkg.in/telebot.v3" ) type USSDHandler struct { handler - conversation conversation.Conversation + state state.State } const ( @@ -19,9 +19,9 @@ const ( func HandleUSSDCommand(c telebot.Context) error { h := &USSDHandler{} - h.setModem(c) - h.conversation = conversation.New(c) - h.conversation.Flow(map[string]telebot.HandlerFunc{ + h.init(c) + h.state = h.stateManager.New(c) + h.state.Stages(map[string]telebot.HandlerFunc{ USSDExecuteCommand: h.handleExecuteCommand, USSDRespondCommand: h.handleRespondCommand, }) @@ -29,28 +29,28 @@ func HandleUSSDCommand(c telebot.Context) error { } func (h *USSDHandler) handle(c telebot.Context) error { - h.conversation.Next(USSDExecuteCommand) + h.state.Next(USSDExecuteCommand) return c.Send("Please send me the USSD command you want to execute") } func (h *USSDHandler) handleExecuteCommand(c telebot.Context) error { response, err := h.modem.RunUSSDCommand(c.Text()) if err != nil { - h.conversation.Done() + h.stateManager.Done(c) c.Send("Failed to execute USSD command, err: " + err.Error()) return err } - h.conversation.Next(USSDRespondCommand) + h.state.Next(USSDRespondCommand) return c.Send(fmt.Sprintf("%s\n%s\nIf you want to respond to this USSD command, please send me the response.", c.Text(), response)) } func (h *USSDHandler) handleRespondCommand(c telebot.Context) error { response, err := h.modem.RespondUSSDCommand(c.Text()) if err != nil { - h.conversation.Done() + h.stateManager.Done(c) c.Send("Failed to respond to USSD command, err: " + err.Error()) return err } - h.conversation.Next(USSDRespondCommand) + h.state.Next(USSDRespondCommand) return c.Send(fmt.Sprintf("%s\n%s\nIf you want to respond to this USSD command, please send me the response.", c.Text(), response)) } diff --git a/internal/app/middleware/modem.go b/internal/app/middleware/modem.go index 5416dec..5b903c6 100644 --- a/internal/app/middleware/modem.go +++ b/internal/app/middleware/modem.go @@ -40,7 +40,6 @@ func SelectModem(requiredEuicc bool) telebot.MiddlewareFunc { } func selectModem(c telebot.Context, modems map[string]*modem.Modem, done chan string) error { - selector := telebot.ReplyMarkup{} btns := make([]telebot.Btn, 0, len(modems)) for k, m := range modems { diff --git a/internal/app/middleware/state.go b/internal/app/middleware/state.go new file mode 100644 index 0000000..8a19d0c --- /dev/null +++ b/internal/app/middleware/state.go @@ -0,0 +1,15 @@ +package middleware + +import ( + "github.com/damonto/telegram-sms/internal/pkg/state" + "gopkg.in/telebot.v3" +) + +func WrapState(state *state.StateManager) telebot.MiddlewareFunc { + return func(next telebot.HandlerFunc) telebot.HandlerFunc { + return func(c telebot.Context) error { + c.Set("state", state) + return next(c) + } + } +} diff --git a/internal/app/routes/router.go b/internal/app/routes/router.go index 4fbb5e6..a80b18f 100644 --- a/internal/app/routes/router.go +++ b/internal/app/routes/router.go @@ -1,16 +1,19 @@ package routes import ( + "github.com/damonto/telegram-sms/internal/pkg/state" "gopkg.in/telebot.v3" ) type Router struct { - bot *telebot.Bot + bot *telebot.Bot + state *state.StateManager } -func NewRouter(bot *telebot.Bot) *Router { +func NewRouter(bot *telebot.Bot, state *state.StateManager) *Router { return &Router{ - bot: bot, + bot: bot, + state: state, } } diff --git a/internal/app/routes/routes.go b/internal/app/routes/routes.go index 896c284..28ece0e 100644 --- a/internal/app/routes/routes.go +++ b/internal/app/routes/routes.go @@ -19,6 +19,8 @@ func (r *Router) commands() map[string]string { } func (r *Router) routes() { + r.bot.Use(mmiddleware.WrapState(r.state)) + r.bot.Handle("/start", handler.HandleStartCommand) { g := r.bot.Group() diff --git a/internal/pkg/conversation/conversation.go b/internal/pkg/conversation/conversation.go deleted file mode 100644 index 7d448cf..0000000 --- a/internal/pkg/conversation/conversation.go +++ /dev/null @@ -1,74 +0,0 @@ -package conversation - -import ( - "log/slog" - "sync" - - "gopkg.in/telebot.v3" -) - -type Conversation interface { - Flow(steps map[string]telebot.HandlerFunc) - Next(next string) - Done() -} - -type conversation struct { - chatId int64 - steps map[string]telebot.HandlerFunc - next string -} - -type conversations struct { - bot *telebot.Bot - mutex sync.Mutex - conversations map[int64]*conversation -} - -var conversationsInstance *conversations - -func NewConversation(bot *telebot.Bot) *conversations { - conversationsInstance = &conversations{ - bot: bot, - conversations: make(map[int64]*conversation, 10), - } - conversationsInstance.handleText() - return conversationsInstance -} - -func (c *conversations) handleText() { - c.bot.Handle(telebot.OnText, func(ctx telebot.Context) error { - if conv, ok := c.conversations[ctx.Chat().ID]; ok { - if step, ok := conv.steps[conv.next]; ok { - return step(ctx) - } - slog.Error("step not found", "step", conv.next) - } - return nil - }) -} - -func New(ctx telebot.Context) Conversation { - conversationsInstance.mutex.Lock() - defer conversationsInstance.mutex.Unlock() - conversation := &conversation{ - chatId: ctx.Chat().ID, - steps: make(map[string]telebot.HandlerFunc, 5), - } - conversationsInstance.conversations[ctx.Chat().ID] = conversation - return conversation -} - -func (c *conversation) Flow(steps map[string]telebot.HandlerFunc) { - c.steps = steps -} - -func (c *conversation) Next(next string) { - c.next = next -} - -func (c *conversation) Done() { - conversationsInstance.mutex.Lock() - defer conversationsInstance.mutex.Unlock() - delete(conversationsInstance.conversations, c.chatId) -} diff --git a/internal/pkg/modem/manager.go b/internal/pkg/modem/manager.go index 4d0bdc8..fa1808a 100644 --- a/internal/pkg/modem/manager.go +++ b/internal/pkg/modem/manager.go @@ -39,7 +39,7 @@ func NewManager() (*Manager, error) { managerInstance = &Manager{ mmgr: mmgr, - modems: make(map[string]*Modem, 10), + modems: make(map[string]*Modem), reboot: make(chan struct{}, 1), } go managerInstance.watch() diff --git a/internal/pkg/state/state.go b/internal/pkg/state/state.go new file mode 100644 index 0000000..3cadc68 --- /dev/null +++ b/internal/pkg/state/state.go @@ -0,0 +1,71 @@ +package state + +import ( + "log/slog" + "sync" + + "gopkg.in/telebot.v3" +) + +type State interface { + Stages(stages map[string]telebot.HandlerFunc) + Next(next string) +} + +type state struct { + chatId int64 + stages map[string]telebot.HandlerFunc + next string +} + +type StateManager struct { + bot *telebot.Bot + mutex sync.Mutex + states map[int64]*state +} + +func NewState(bot *telebot.Bot) *StateManager { + s := &StateManager{ + bot: bot, + states: make(map[int64]*state, 10), + } + s.handleText() + return s +} + +func (c *StateManager) handleText() { + c.bot.Handle(telebot.OnText, func(ctx telebot.Context) error { + if state, ok := c.states[ctx.Chat().ID]; ok { + if step, ok := state.stages[state.next]; ok { + return step(ctx) + } + slog.Error("stage not found", "stage", state.next) + } + return nil + }) +} + +func (s *StateManager) New(ctx telebot.Context) State { + s.mutex.Lock() + defer s.mutex.Unlock() + state := &state{ + chatId: ctx.Chat().ID, + stages: make(map[string]telebot.HandlerFunc), + } + s.states[ctx.Chat().ID] = state + return state +} + +func (s *StateManager) Done(ctx telebot.Context) { + s.mutex.Lock() + defer s.mutex.Unlock() + delete(s.states, ctx.Chat().ID) +} + +func (c *state) Stages(stages map[string]telebot.HandlerFunc) { + c.stages = stages +} + +func (c *state) Next(next string) { + c.next = next +} diff --git a/main.go b/main.go index bf85f6a..3d5dc6d 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "log/slog" + "net/http" "os" "os/signal" "time" @@ -52,8 +53,14 @@ func main() { slog.Info("You are using", "version", Version) bot, err := telebot.NewBot(telebot.Settings{ - Token: config.C.BotToken, - Poller: &telebot.LongPoller{Timeout: 10 * time.Second}, + Token: config.C.BotToken, + Client: &http.Client{ + Timeout: 30 * time.Second, + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + }, + }, + Poller: &telebot.LongPoller{Timeout: 30 * time.Second}, }) if err != nil { slog.Error("failed to create bot", "error", err)