From af8982be2db3515656e614ee194fa4f10c9b7b91 Mon Sep 17 00:00:00 2001 From: nefrace Date: Thu, 19 Jan 2023 23:08:59 +0300 Subject: [PATCH] initial --- bot.go | 35 +++++++++ keyboards.go | 104 +++++++++++++++++++++++++++ nechotron.go | 43 +++++++++++ state.go | 23 ++++++ update.go | 196 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 401 insertions(+) create mode 100644 bot.go create mode 100644 keyboards.go create mode 100644 nechotron.go create mode 100644 state.go create mode 100644 update.go diff --git a/bot.go b/bot.go new file mode 100644 index 0000000..fef2c3e --- /dev/null +++ b/bot.go @@ -0,0 +1,35 @@ +package nechotron + +import ( + "context" + "time" + + echo "github.com/NicoNex/echotron/v3" + "github.com/google/uuid" +) + +type bot struct { + chatID int64 + me *echo.User + echo.API + state *State +} + +func (b *bot) Update(u *echo.Update) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + upd := &Update{ + U: U(*u), + Bot: b, + UpdateID: uuid.New(), + Ctx: ctx, + } + + newState, err := b.state.Fn(upd) + if err != nil { + upd.LogError("", err, true) + } + if newState != nil { + b.state = newState + } +} diff --git a/keyboards.go b/keyboards.go new file mode 100644 index 0000000..e6228ca --- /dev/null +++ b/keyboards.go @@ -0,0 +1,104 @@ +package nechotron + +import "github.com/NicoNex/echotron/v3" + +type InKeyboard struct { + Buttons [][]echotron.InlineKeyboardButton +} + +func (i *InKeyboard) Row(buttons ...echotron.InlineKeyboardButton) *InKeyboard { + i.Buttons = append(i.Buttons, buttons) + return i +} + +func (i *InKeyboard) Markup() echotron.InlineKeyboardMarkup { + if len(i.Buttons) == 0 { + return echotron.InlineKeyboardMarkup{ + InlineKeyboard: [][]echotron.InlineKeyboardButton{}, + } + } + return echotron.InlineKeyboardMarkup{ + InlineKeyboard: i.Buttons, + } +} + +func InButtonURL(text string, url string) echotron.InlineKeyboardButton { + return echotron.InlineKeyboardButton{ + Text: text, + URL: url, + } +} + +func InButtonCallback(text string, callback string) echotron.InlineKeyboardButton { + return echotron.InlineKeyboardButton{ + Text: text, + CallbackData: callback, + } +} + +type ReKeyboard struct { + Buttons [][]echotron.KeyboardButton +} + +func (i *ReKeyboard) Row(buttons ...echotron.KeyboardButton) *ReKeyboard { + i.Buttons = append(i.Buttons, buttons) + return i +} + +func (i *ReKeyboard) Markup(oneTime bool) echotron.ReplyKeyboardMarkup { + if len(i.Buttons) == 0 { + return echotron.ReplyKeyboardMarkup{ + OneTimeKeyboard: oneTime, + ResizeKeyboard: true, + Keyboard: [][]echotron.KeyboardButton{}, + } + } + + return echotron.ReplyKeyboardMarkup{ + OneTimeKeyboard: oneTime, + ResizeKeyboard: true, + Keyboard: i.Buttons, + } +} + +func ReButton(text string) echotron.KeyboardButton { + return echotron.KeyboardButton{ + Text: text, + } +} + +type Button struct { + text string + query string + url string +} + +func NewButton() *Button { + return &Button{} +} + +func (b *Button) ReplyButton() echotron.KeyboardButton { + return echotron.KeyboardButton{Text: b.text} +} + +func (b *Button) InlineCallback() echotron.InlineKeyboardButton { + return echotron.InlineKeyboardButton{CallbackData: b.query} +} +func (b *Button) InlineURL() echotron.InlineKeyboardButton { + return echotron.InlineKeyboardButton{URL: b.url} +} + +func (b *Button) Text(text string) *Button { + b.text = text + return b +} + +func (b *Button) Query(query string) *Button { + b.query = query + return b +} + +func (b *Button) URL(url string) *Button { + b.url = url + return b +} diff --git a/nechotron.go b/nechotron.go new file mode 100644 index 0000000..00be9cb --- /dev/null +++ b/nechotron.go @@ -0,0 +1,43 @@ +package nechotron + +import ( + "log" + + echo "github.com/NicoNex/echotron/v3" +) + +type Nechotron struct { + Token string + DefaultState *State +} + +func NewTron(token string, defaultState *State) *Nechotron { + state := defaultState + if state == nil { + state = &EchoState + } + return &Nechotron{ + Token: token, + DefaultState: state, + } +} + +func (n *Nechotron) newBot(chatID int64) echo.Bot { + a := echo.NewAPI(n.Token) + me, _ := a.GetMe() + // log.Println("New bot active: ", chatID, me.Result) + b := &bot{ + chatID: chatID, + me: me.Result, + API: a, + state: n.DefaultState, + } + b.state = n.DefaultState + return b +} + +func (n *Nechotron) DispatchPoll() error { + dispatcher := echo.NewDispatcher(n.Token, n.newBot) + log.Println("Nechotron poll dispatcher started") + return dispatcher.Poll() +} diff --git a/state.go b/state.go new file mode 100644 index 0000000..9379422 --- /dev/null +++ b/state.go @@ -0,0 +1,23 @@ +package nechotron + +import ( + "context" + + "github.com/NicoNex/echotron/v3" +) + +type State struct { + Fn stateFn + Data context.Context +} + +type stateFn func(*Update) (*State, error) + +var EchoState = State{ + Fn: EchoFunc, +} + +func EchoFunc(u *Update) (*State, error) { + u.AnswerText(u.Text(), &echotron.MessageOptions{}) + return nil, nil +} diff --git a/update.go b/update.go new file mode 100644 index 0000000..2e1ab8b --- /dev/null +++ b/update.go @@ -0,0 +1,196 @@ +package nechotron + +import ( + "context" + "fmt" + "log" + + "github.com/NicoNex/echotron/v3" + "github.com/google/uuid" +) + +type U echotron.Update + +var emptyOpts = echotron.MessageOptions{} + +type Update struct { + U + Bot *bot + UpdateID uuid.UUID + Ctx context.Context +} + +func (u *Update) Upd() *echotron.Update { + return (*echotron.Update)(&u.U) +} + +func (u *Update) ChatID() int64 { + if u.IsMessage() { + return u.Message.Chat.ID + } + if u.IsCallback() { + return u.CallbackQuery.Message.Chat.ID + } + log.Fatalf("[%s] Can't get ChatID of update %v+", u.UpdateID, u.U) + return 0 +} +func (u *Update) MessageID() int { + if u.IsMessage() { + return u.Message.ID + } + if u.IsCallback() { + return u.CallbackQuery.Message.ID + } + log.Fatalf("[%s] Can't get ChatID of update %v+", u.UpdateID, u.U) + return 0 +} + +func (u *Update) AnswerText(text string, options *echotron.MessageOptions) (echotron.APIResponseMessage, error) { + return u.Bot.SendMessage(text, u.ChatID(), options) +} + +func (u *Update) EditText(text string, options *echotron.MessageTextOptions) (echotron.APIResponse, error) { + return u.Bot.EditMessageText(text, echotron.NewMessageID(u.ChatID(), u.MessageID()), options) +} + +func (u *Update) AnswerPlain(text string) (echotron.APIResponseMessage, error) { + return u.Bot.SendMessage(text, u.ChatID(), &emptyOpts) +} + +func (u *Update) AnswerMarkdown(text string) (echotron.APIResponseMessage, error) { + return u.Bot.SendMessage(text, u.ChatID(), &echotron.MessageOptions{ + ParseMode: echotron.MarkdownV2, + }) +} + +func (u *Update) DeleteMessage() (echotron.APIResponse, error) { + return u.Bot.DeleteMessage(u.ChatID(), u.MessageID()) +} + +type MessageType uint8 + +const ( + MESSAGE_UNKNOWN MessageType = iota + MESSAGE_TEXT + MESSAGE_PHOTO + MESSAGE_VIDEO + MESSAGE_DOCUMENT + MESSAGE_AUDIO + MESSAGE_VOICE + MESSAGE_VIDEONOTE + MESSAGE_CALLBACK +) + +func (u *Update) IsMessage() bool { + return u.Message != nil +} +func (u *Update) IsCallback() bool { + return u.CallbackQuery != nil +} +func (u *Update) IsButton(b *Button) bool { + if u.IsText() && u.Text() == b.text { + return true + } + if u.IsCallback() && u.CallbackQuery.Data == b.query { + return true + } + return false +} + +func (u *Update) IsText() bool { + return u.IsMessage() && u.Message.Text != "" +} +func (u *Update) IsPhoto() bool { + return u.IsMessage() && u.Message.Photo != nil +} +func (u *Update) IsVideo() bool { + return u.IsMessage() && u.Message.Video != nil +} +func (u *Update) IsDocument() bool { + return u.IsMessage() && u.Message.Document != nil +} +func (u *Update) IsAudio() bool { + return u.IsMessage() && u.Message.Audio != nil +} +func (u *Update) IsVoice() bool { + return u.IsMessage() && u.Message.Voice != nil +} +func (u *Update) IsVideoNote() bool { + return u.IsMessage() && u.Message.VideoNote != nil +} + +func (u *Update) Type() MessageType { + if u.IsCallback() { + return MESSAGE_CALLBACK + } + if u.IsText() { + return MESSAGE_TEXT + } + if u.IsAudio() { + return MESSAGE_AUDIO + } + if u.IsVideo() { + return MESSAGE_VIDEO + } + if u.IsPhoto() { + return MESSAGE_PHOTO + } + if u.IsDocument() { + return MESSAGE_DOCUMENT + } + if u.IsVoice() { + return MESSAGE_VOICE + } + if u.IsVideoNote() { + return MESSAGE_VIDEONOTE + } + return MESSAGE_UNKNOWN +} + +func (u *Update) From() *echotron.User { + if u.IsMessage() { + return u.Message.From + } + return u.CallbackQuery.From +} + +func (u *Update) Text() string { + if u.IsText() { + return u.Message.Text + } + return u.Message.Caption +} + +func (u *Update) Entities() []*echotron.MessageEntity { + if u.IsText() { + return u.Message.Entities + } + return u.Message.CaptionEntities +} + +func (u *Update) IsUserAdmin() bool { + ids := []int64{ + 60441930, // v.rud + 327487258, // vika shet + } + from := u.From() + for i := range ids { + if from.ID == ids[i] { + return true + } + } + return false +} + +func (u *Update) LogError(text string, e error, send bool) { + if text != "" { + text = "Ошибка: " + text + } else { + text = "Произошла внутренняя ошибка" + } + log.Printf("[%s] %s: %v", u.UpdateID.String(), text, e) + if send { + u.AnswerMarkdown(fmt.Sprintf("%s\nКод запроса: `%s`", text, u.UpdateID.String())) + } + +}