package laniakea import ( "bytes" "encoding/json" "fmt" "io" "net/http" "strings" ) type Bot struct { token string logger *Logger debug bool plugins []*Plugin prefixes []string updateOffset int updateQueue *Queue[*Update] } type MsgContext struct { Bot *Bot Msg *Message FromID int Prefix string Text string } func NewBot(token string) *Bot { logger := CreateLogger() logger.SetLevel(DEBUG) updateQueue := CreateQueue[*Update](256) bot := &Bot{ updateOffset: 0, plugins: make([]*Plugin, 0), debug: false, prefixes: make([]string, 0), updateQueue: updateQueue, token: token, logger: logger, } return bot } func (b *Bot) AddPrefixes(prefixes ...string) { b.prefixes = append(b.prefixes, prefixes...) } func (b *Bot) Run() { if len(b.prefixes) == 0 { b.logger.Fatal("no prefixes defined") return } if len(b.plugins) == 0 { b.logger.Fatal("no plugins defined") return } go func() { for { _, err := b.GetUpdates() if err != nil { b.logger.Error(err) } } }() for { queue := b.GetQueue() if queue.IsEmpty() { continue } u := queue.Dequeue() b.handleMessage(u) } } func (b *Bot) handleMessage(update *Update) { text := strings.TrimSpace(update.Message.Text) prefix, hasPrefix := b.checkPrefixes(text) if !hasPrefix { return } text = strings.TrimSpace(text[len(prefix):]) for _, plugin := range b.plugins { for cmd, _ := range plugin.Commands { if !strings.HasPrefix(text, cmd) { continue } ctx := &MsgContext{ Bot: b, FromID: update.Message.From.ID, Msg: update.Message, Prefix: prefix, Text: text[len(cmd):], } plugin.Execute(cmd, ctx) } } } func (b *Bot) checkPrefixes(text string) (string, bool) { for _, prefix := range b.prefixes { if strings.HasPrefix(text, prefix) { return prefix, true } } return "", false } func (b *Bot) AddPlugins(plugin ...*Plugin) { b.plugins = append(b.plugins, plugin...) } func (b *Bot) SetDebug(debug bool) { b.debug = debug } func (b *Bot) GetQueue() *Queue[*Update] { return b.updateQueue } func (ctx *MsgContext) Answer(text string) { _, err := ctx.Bot.SendMessage(&SendMessageP{ ChatID: ctx.Msg.Chat.ID, Text: text, }) if err != nil { ctx.Bot.logger.Error(err) } } type ApiResponse struct { Ok bool `json:"ok"` Result map[string]interface{} `json:"result,omitempty"` Description string `json:"description,omitempty"` ErrorCode int `json:"error_code,omitempty"` } type ApiResponseA struct { Ok bool `json:"ok"` Result []interface{} `json:"result,omitempty"` Description string `json:"description,omitempty"` ErrorCode int `json:"error_code,omitempty"` } // request is a low-level call to api. func (b *Bot) request(methodName string, params map[string]interface{}) (map[string]interface{}, error) { var buf bytes.Buffer err := json.NewEncoder(&buf).Encode(params) if err != nil { return nil, err } if b.debug { b.logger.Debug(fmt.Sprintf("POST https://api.telegram.org/bot%s/%s %s", "", methodName, string(buf.Bytes()))) } r, err := http.Post(fmt.Sprintf("https://api.telegram.org/bot%s/%s", b.token, methodName), "application/json", &buf) if err != nil { return nil, err } data, err := io.ReadAll(r.Body) if err != nil { return nil, err } response := new(ApiResponse) var result map[string]interface{} err = json.Unmarshal(data, &response) if err != nil { responseArray := new(ApiResponseA) err = json.Unmarshal(data, responseArray) if err != nil { return nil, err } result = map[string]interface{}{ "data": responseArray.Result, } } else { result = response.Result } if !response.Ok { return nil, fmt.Errorf("[%d] %s", response.ErrorCode, response.Description) } return result, err }