package tg import ( "errors" "sort" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" ) type Chat = tgbotapi.Chat type User = tgbotapi.User // The wrapper around Telegram API. type Bot struct { // Custom data value. data any api *tgbotapi.BotAPI me User // Private bot behaviour. behaviour *Behaviour // Group bot behaviour. //groupBehaviour *GroupBehaviour // Bot behaviour in channels. //channelBehaviour *ChannelBehaviour sessions SessionMap //groupSessions GroupSessionMap } // Return the new bot with empty sessions and behaviour. func NewBot(token string) (*Bot, error) { bot, err := tgbotapi.NewBotAPI(token) if err != nil { return nil, err } return &Bot{ api: bot, }, nil } func (bot *Bot) SetDebug(debug bool) *Bot { bot.api.Debug = debug return bot } func (bot *Bot) Api() *tgbotapi.BotAPI { return bot.api } func (bot *Bot) Me() User { return bot.me } // Send the Renderable to the specified session client side. // Can be used for both group and private sessions because // SessionId represents both for chat IDs. func (bot *Bot) Send( sid SessionId, v Sendable, ) (*Message, error) { config := v.SendConfig(sid, bot) if config.Error != nil { return nil, config.Error } msg, err := bot.api.Send(config.ToApi()) if err != nil { return nil, err } v.SetMessage(&msg) return &msg, nil } func (bot *Bot) Sendf( sid SessionId, format string, v ...any, ) (*Message, error){ return bot.Send( sid, Messagef(format, v...), ) } // Send to the session specified its ID raw chattable from the tgbotapi. func (bot *Bot) SendRaw( sid SessionId, v tgbotapi.Chattable, ) (*Message, error) { msg, err := bot.api.Send(v) if err != nil { return nil, err } return &msg, nil } // Get session by its ID. Can be used for any scope // including private, group and channel. func (bot *Bot) GotSession( sid SessionId, ) (*Session, bool) { session, ok := bot.sessions[sid] return session, ok } func (bot *Bot) SetData(v any) *Bot { bot.data = v return bot } func (bot *Bot) Data() any { return bot.data } func (b *Bot) SetBehaviour(beh *Behaviour) *Bot { b.behaviour = beh b.sessions = make(SessionMap) return b } func (b *Bot) SetSessions(sessions SessionMap) *Bot { b.sessions = sessions return b } /*func (b *Bot) WithGroupBehaviour(beh *GroupBehaviour) *Bot { b.groupBehaviour = beh b.groupSessions = make(GroupSessionMap) return b } func (b *Bot) WithGroupSessions(sessions GroupSessionMap) *Bot { b.groupSessions = sessions return b }*/ func (bot *Bot) DeleteCommands() { //tgbotapi.NewBotCommandScopeAllPrivateChats(), cfg := tgbotapi.NewDeleteMyCommands() bot.api.Request(cfg) } // Setting the command on the user side. func (bot *Bot) SetCommands( scope tgbotapi.BotCommandScope, cmdMap CommandMap, ) error { // First the private commands. names := []string{} for name := range cmdMap { names = append(names, string(name)) } sort.Strings(names) cmds := []Command{} for _, name := range names { cmds = append( cmds, cmdMap[CommandName(name)], ) } botCmds := []tgbotapi.BotCommand{} for _, cmd := range cmds { botCmds = append(botCmds, cmd.ToApi()) } //tgbotapi.NewBotCommandScopeAllPrivateChats(), cfg := tgbotapi.NewSetMyCommandsWithScope( scope, botCmds..., ) _, err := bot.api.Request(cfg) if err != nil { return err } return nil } // Run the bot with the Behaviour. func (bot *Bot) Run() error { if bot.behaviour == nil { return errors.New("no behaviour defined") } if bot.behaviour != nil && bot.behaviour.Root == nil { return errors.New("the root widget is not set, cannot run") } uc := tgbotapi.NewUpdate(0) uc.Timeout = 10 updates := bot.api.GetUpdatesChan(uc) handles := make(map[string] chan Update) if bot.behaviour != nil { chn := make(chan Update) handles["private"] = chn go bot.handlePrivate(chn) } /*if bot.groupBehaviour != nil { commanders := make(map[CommandName] BotCommander) for k, v := range bot.groupBehaviour.Commands { commanders[k] = v } bot.SetCommands( tgbotapi.NewBotCommandScopeAllGroupChats(), commanders, ) chn := make(chan *Update) handles["group"] = chn handles["supergroup"] = chn go bot.handleGroup(chn) }*/ me, _ := bot.Api().GetMe() bot.me = me for up := range updates { u := Update{ Update: up, } // Sometimes returns nil. fromChat := u.FromChat() if fromChat == nil { continue } chn, ok := handles[fromChat.Type] // Skipping non existent scope. if !ok { continue } chn <- u } return nil } // The function handles updates supposed for the private // chat with the bot. func (bot *Bot) handlePrivate(updates chan Update) { var sid SessionId for u := range updates { sid = SessionId(u.FromChat().ID) session, sessionOk := bot.sessions[sid] if u.Message != nil && !sessionOk { // Creating session if we have none // but only on text messages. session = bot.sessions.Add(bot, sid, PrivateSessionScope) // Creating the root context // that takes updates directly from // the session. rootContext := Context{ session: session, update: u, input: session.updates, } go rootContext.serve() rootContext.input.Send(u) continue } if sessionOk { session.updates.Send(u) } } }